From 851483e3100de927532bebc01297f2450cabfdaf Mon Sep 17 00:00:00 2001 From: NGnius Date: Mon, 30 Dec 2019 12:51:28 -0500 Subject: [PATCH] Add initial Python IDE behaviour --- .gitignore | 2 + .../Commands/DebugCommandEngine.cs | 42 ++++++++++- .../Commands/PythonRunnerCommandEngine.cs | 54 ++------------ .../Environment/PythonEnvironment.cs | 73 +++++++++++++++++++ GamecraftScripting/GamecraftScripting.csproj | 1 + 5 files changed, 123 insertions(+), 49 deletions(-) create mode 100644 GamecraftScripting/Environment/PythonEnvironment.cs diff --git a/.gitignore b/.gitignore index 557f793..ad28608 100644 --- a/.gitignore +++ b/.gitignore @@ -450,3 +450,5 @@ dmypy.json # Pyre type checker .pyre/ +# Gamecraft install folder +ref diff --git a/GamecraftScripting/Commands/DebugCommandEngine.cs b/GamecraftScripting/Commands/DebugCommandEngine.cs index 96274ba..3900248 100644 --- a/GamecraftScripting/Commands/DebugCommandEngine.cs +++ b/GamecraftScripting/Commands/DebugCommandEngine.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Utility; using Svelto.ECS; +using uREPL; using IronPython; @@ -20,19 +21,54 @@ namespace GamecraftScripting.Commands public IEntitiesDB entitiesDB { set; private get; } + private bool isEnabledIDE = false; + public void Dispose() { - CommandRegistrationHelper.Unregister("PythonInfo"); + CommandRegistrationHelper.Unregister("PythonVersion"); + CommandRegistrationHelper.Unregister("PythonSearchPathes"); + CommandRegistrationHelper.Unregister("ToggleIDE"); + if (isEnabledIDE) + { + toggleIDE(); + } } public void Ready() { - CommandRegistrationHelper.Register("PythonInfo", ironPythonInfo, "Display Python debug info"); + CommandRegistrationHelper.Register("PythonVersion", ironPythonVersion, "Display Python info"); + CommandRegistrationHelper.Register("PythonSearchPathes", ironPythonPathes, "Display Python import search pathes"); + CommandRegistrationHelper.Register("ToggleIDE", toggleIDE, "Toggle command line IDE tools"); } - private void ironPythonInfo() + private void ironPythonVersion() { Logging.CommandLog($"Assembly {typeof(PythonOptions).Assembly.GetName().ToString()}"); } + + private void ironPythonPathes() + { + Logging.CommandLog("'"+string.Join("', '", Environment.PythonEnvironment.CurrentEngine.GetSearchPaths().ToArray())+"'"); + } + + private void toggleIDE() + { + uREPL.Parameters cliConfig = uREPL.Window.selected.parameters; + if (isEnabledIDE) + { + // disable + } + else + { + // enable + } + isEnabledIDE = !isEnabledIDE; + cliConfig.useMonoCompletion = isEnabledIDE; + cliConfig.useGameObjectNameCompletion = isEnabledIDE; + cliConfig.useGameObjectPathCompletion = isEnabledIDE; + cliConfig.useGlobalClassCompletion = isEnabledIDE; + string word = isEnabledIDE ? "On" : "Off"; + Logging.CommandLog($"IDE {word}"); + } } } diff --git a/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs b/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs index ae04810..eaaeef3 100644 --- a/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs +++ b/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs @@ -1,18 +1,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.Numerics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Security.Policy; using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Utility; using Svelto.ECS; -using RobocraftX.Common; -using IronPython.Hosting; using Microsoft.Scripting.Hosting; using GamecraftScripting.Environment; @@ -27,10 +20,6 @@ namespace GamecraftScripting.Commands public IEntitiesDB entitiesDB { set; private get; } - private ScriptEngine pyEngine; - - private ScriptScope gameScope; - public void Dispose() { CommandRegistrationHelper.Unregister("RunPythonScript"); @@ -42,14 +31,11 @@ namespace GamecraftScripting.Commands CommandRegistrationHelper.Register("RunPythonScript", RunPythonScript, "Run a python script stored on your computer"); CommandRegistrationHelper.Register("RunPython", RunPythonString, "Run a python argument"); // create insecure Python namespace - CreateFreeInstance(); - // make "import GamecraftModdingAPI" work without the clr boilerplate - pyEngine.Execute("import clr\nclr.AddReference(\"GamecraftModdingAPI\")", gameScope); - ICollection searchPaths = pyEngine.GetSearchPaths(); - searchPaths.Add(@".\Lib\"); - searchPaths.Add(@".\Gamecraft_Data\Managed\Lib\"); - pyEngine.SetSearchPaths(searchPaths); - Logging.MetaLog(string.Join(", ", pyEngine.GetSearchPaths().ToArray())); + ScriptEngine pyEngine = PythonEnvironment.CreateEngine(); + ScriptScope pyScope = PythonEnvironment.CreateScope(pyEngine); + PythonEnvironment.CurrentEngine = pyEngine; + PythonEnvironment.CurrentScope = pyScope; + //Logging.MetaLog(string.Join(", ", pyEngine.GetSearchPaths().ToArray())); } private void RunPythonScript(string scriptPath) @@ -65,10 +51,10 @@ namespace GamecraftScripting.Commands private void ExecuteScript(string script, bool logError=true) { - ScriptSource src = pyEngine.CreateScriptSourceFromString(script); + ScriptSource src = PythonEnvironment.CurrentEngine.CreateScriptSourceFromString(script); try { - src.Execute(gameScope); + src.Execute(PythonEnvironment.CurrentScope); } catch (Exception e) { @@ -84,33 +70,9 @@ namespace GamecraftScripting.Commands } } - /// - /// [Experimental] Create a Python instance without access to any(?) assemblies - /// - private void CreateSandboxedInstance() - { - Evidence evidence = new Evidence(); // an uninformative class name to limit accessible Assemblies - AppDomain domain = AppDomain.CreateDomain(GameMode.SaveGameDetails.Name, evidence); - this.pyEngine = Python.CreateEngine(domain); - PythonLogStream pyLog = new PythonLogStream(); - pyEngine.Runtime.IO.SetOutput(pyLog, pyLog.Encoding); - this.gameScope = pyEngine.CreateScope(); - } - - /// - /// Create an unsecured Python instance - /// - private void CreateFreeInstance() - { - this.pyEngine = Python.CreateEngine(); - PythonLogStream pyLog = new PythonLogStream(); - pyEngine.Runtime.IO.SetOutput(pyLog, pyLog.Encoding); - this.gameScope = pyEngine.CreateScope(); - } - public PythonRunnerCommandEngine() { - Logging.CommandLog($"cwd: {Directory.GetCurrentDirectory()}"); + //Logging.CommandLog($"cwd: {Directory.GetCurrentDirectory()}"); } } } diff --git a/GamecraftScripting/Environment/PythonEnvironment.cs b/GamecraftScripting/Environment/PythonEnvironment.cs new file mode 100644 index 0000000..44ae35d --- /dev/null +++ b/GamecraftScripting/Environment/PythonEnvironment.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Security.Policy; + +using RobocraftX.Common; + +using IronPython.Hosting; +using Microsoft.Scripting.Hosting; + +namespace GamecraftScripting.Environment +{ + /// + /// Python environment factories and utilities + /// + public static class PythonEnvironment + { + public static ScriptEngine CurrentEngine { get; set; } + + public static ScriptScope CurrentScope { get; set; } + + /// + /// Creates and configure the Python execution engine + /// + /// The Python engine + public static ScriptEngine CreateEngine() + { + ScriptEngine pyEngine = Python.CreateEngine(); + PythonLogStream pyLog = new PythonLogStream(); + pyEngine.Runtime.IO.SetOutput(pyLog, pyLog.Encoding); + ICollection searchPaths = pyEngine.GetSearchPaths(); + // Add Python standard lib to import search pathes + searchPaths.Add(@".\Lib\"); // try not to use this location + searchPaths.Add(@".\Gamecraft_Data\Managed\Lib\"); + pyEngine.SetSearchPaths(searchPaths); + return pyEngine; + } + + /// + /// Creates a restricted Python execution engine + /// + /// The Python engine + public static ScriptEngine CreateRestrictedEngine() + { + Evidence evidence = new Evidence(); // an uninformative class name to limit accessible Assemblies + AppDomain domain = AppDomain.CreateDomain(GameMode.SaveGameDetails.Name, evidence); + ScriptEngine pyEngine = Python.CreateEngine(domain); + // Add Python standard lib to import search pathes + ICollection searchPaths = pyEngine.GetSearchPaths(); + searchPaths.Add(@".\Lib\"); // try not to use this location + searchPaths.Add(@".\Gamecraft_Data\Managed\Lib\"); + if (!string.IsNullOrWhiteSpace(GameMode.SaveGameDetails.Folder)) + { + // Add game's working directory, in case multiple files are used + searchPaths.Add(GameMode.SaveGameDetails.Folder); + } + pyEngine.SetSearchPaths(searchPaths); + return pyEngine; + } + + /// + /// Creates the Python execution scope + /// + /// The Python scope + /// The Python engine + public static ScriptScope CreateScope(ScriptEngine pyEngine) + { + ScriptScope pyScope = pyEngine.CreateScope(); + // make "import GamecraftModdingAPI" work without the clr boilerplate + pyEngine.Execute("import clr\nclr.AddReference(\"GamecraftModdingAPI\")", pyScope); + return pyScope; + } + } +} diff --git a/GamecraftScripting/GamecraftScripting.csproj b/GamecraftScripting/GamecraftScripting.csproj index 6fc5a51..39c6de7 100644 --- a/GamecraftScripting/GamecraftScripting.csproj +++ b/GamecraftScripting/GamecraftScripting.csproj @@ -2,6 +2,7 @@ net48 + net472 true 0.0.1.0 Exmods