From 921a754ebbb3f4f345bae30ed313abac09d5075a Mon Sep 17 00:00:00 2001 From: yhh <359807859@qq.com> Date: Fri, 24 Aug 2018 11:12:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Ecsharp=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=BC=95=E6=93=8E=20=E4=BC=98=E5=8C=96lua=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLEditor.Core/CLEditor.Core.csproj | 1 + CLEditor.Core/IBehaviour.cs | 38 ++ CLEditor.sln | 14 + .../CLEngine.ScriptEngine.csproj | 145 +++++++ .../Properties/AssemblyInfo.cs | 36 ++ CLEngine.ScriptEngine/ScriptEngine.cs | 115 ++++++ CLEngine.ScriptEngine/Scripting.Extensions.cs | 295 +++++++++++++++ CLEngine.ScriptEngine/Scripting.evaluator.cs | 345 +++++++++++++++++ CLEngine.ScriptEngine/Scripting.native.cs | 354 ++++++++++++++++++ CLEngine.ScriptEngine/app.config | 43 +++ CLEngine.ScriptEngine/packages.config | 52 +++ CLEngine/CGame.cs | 156 +++++++- CLEngine/CLEngine.csproj | 10 +- CLEngine/GameExtensions.cs | 20 + CLEngine/Properties/Resources.Designer.cs | 14 +- CLEngine/Properties/Resources.resx | 5 +- CLEngine/Templates/Core.cs | 52 +++ CLEngine/Templates/core.lua | 30 +- Game.Windows/Game.Windows.csproj | 6 +- Game.Windows/app.config | 3 + 20 files changed, 1706 insertions(+), 28 deletions(-) create mode 100644 CLEditor.Core/IBehaviour.cs create mode 100644 CLEngine.ScriptEngine/CLEngine.ScriptEngine.csproj create mode 100644 CLEngine.ScriptEngine/Properties/AssemblyInfo.cs create mode 100644 CLEngine.ScriptEngine/ScriptEngine.cs create mode 100644 CLEngine.ScriptEngine/Scripting.Extensions.cs create mode 100644 CLEngine.ScriptEngine/Scripting.evaluator.cs create mode 100644 CLEngine.ScriptEngine/Scripting.native.cs create mode 100644 CLEngine.ScriptEngine/app.config create mode 100644 CLEngine.ScriptEngine/packages.config create mode 100644 CLEngine/GameExtensions.cs create mode 100644 CLEngine/Templates/Core.cs create mode 100644 Game.Windows/app.config diff --git a/CLEditor.Core/CLEditor.Core.csproj b/CLEditor.Core/CLEditor.Core.csproj index 35a5a71..ef46f84 100644 --- a/CLEditor.Core/CLEditor.Core.csproj +++ b/CLEditor.Core/CLEditor.Core.csproj @@ -85,6 +85,7 @@ + diff --git a/CLEditor.Core/IBehaviour.cs b/CLEditor.Core/IBehaviour.cs new file mode 100644 index 0000000..e1cdc61 --- /dev/null +++ b/CLEditor.Core/IBehaviour.cs @@ -0,0 +1,38 @@ +using Microsoft.Xna.Framework; + +namespace CLEngine +{ + /// + /// 游戏行为类 + /// + public interface IBehaviour + { + /// + /// 初始化游戏 + /// + void Initialize(); + + /// + /// 游戏绘制 + /// + /// + void Draw(GameTime gameTime); + + /// + /// 游戏更新 + /// + /// + void Update(GameTime gameTime); + + /// + /// 加载资源 + /// + void LoadContent(); + + /// + /// 卸载资源 + /// + void UnLoadContent(); + + } +} \ No newline at end of file diff --git a/CLEditor.sln b/CLEditor.sln index fd3e8c4..cdb6d19 100644 --- a/CLEditor.sln +++ b/CLEditor.sln @@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game.Windows", "Game.Window EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Game.Desktop", "Game.Desktop\Game.Desktop.csproj", "{4961F01A-591A-457C-9485-2935A428287A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLEngine.ScriptEngine", "CLEngine.ScriptEngine\CLEngine.ScriptEngine.csproj", "{DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -151,6 +153,18 @@ Global {4961F01A-591A-457C-9485-2935A428287A}.Release|x64.Build.0 = Release|Any CPU {4961F01A-591A-457C-9485-2935A428287A}.Release|x86.ActiveCfg = Release|Any CPU {4961F01A-591A-457C-9485-2935A428287A}.Release|x86.Build.0 = Release|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Debug|x64.Build.0 = Debug|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Debug|x86.Build.0 = Debug|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Release|Any CPU.Build.0 = Release|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Release|x64.ActiveCfg = Release|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Release|x64.Build.0 = Release|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Release|x86.ActiveCfg = Release|Any CPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CLEngine.ScriptEngine/CLEngine.ScriptEngine.csproj b/CLEngine.ScriptEngine/CLEngine.ScriptEngine.csproj new file mode 100644 index 0000000..af9a75c --- /dev/null +++ b/CLEngine.ScriptEngine/CLEngine.ScriptEngine.csproj @@ -0,0 +1,145 @@ + + + + + Debug + AnyCPU + {DE6B0459-39BA-49B6-8B63-1BB3A8B5105B} + Library + Properties + CLEngine.ScriptEngine + CLEngine.ScriptEngine + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;C_PLATFORM_WINDOWS_DESKTOP + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\CS-Script.bin.3.28.4.0\lib\net46\CSScriptLibrary.dll + + + ..\packages\Microsoft.CodeAnalysis.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + ..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.0.0\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + ..\packages\CS-Script.bin.3.28.4.0\lib\net46\Mono.CSharp.dll + + + + ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + + ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + + ..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + + + ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + + ..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + + + + + + + + ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + + + ..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + + + ..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + + + + + + + + + + + + + + + + + + + + {A9459A99-39D8-480B-BF14-A7687ECE7DB1} + CLEditor.Core + + + + \ No newline at end of file diff --git a/CLEngine.ScriptEngine/Properties/AssemblyInfo.cs b/CLEngine.ScriptEngine/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f69f602 --- /dev/null +++ b/CLEngine.ScriptEngine/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("CLEngine.ScriptEngine")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CLEngine.ScriptEngine")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("de6b0459-39ba-49b6-8b63-1bb3a8b5105b")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CLEngine.ScriptEngine/ScriptEngine.cs b/CLEngine.ScriptEngine/ScriptEngine.cs new file mode 100644 index 0000000..981e107 --- /dev/null +++ b/CLEngine.ScriptEngine/ScriptEngine.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Reflection; +using csscript; +using CSScriptLibrary; +using CSScriptNativeApi; + +namespace CLEngine.ScriptEngine +{ + /// + /// 脚本引擎提供脚本编译及脚本调试等完整功能 + /// + public class ScriptEngine + { + /// + /// 脚本引擎版本 + /// + public string ScriptEngineVersion => Environment.GetEnvironmentVariable("CSScriptRuntime"); + /// + /// 脚本引擎位置 + /// + public string ScriptEngineLocation => Environment.GetEnvironmentVariable("CSScriptRuntimeLocation"); + +#if C_PLATFORM_WINDOWS_DESKTOP + /// + /// NuGet包脚本的位置可以加载/引用 + /// + public string ScriptEngineNuget => Environment.GetEnvironmentVariable("css_nuget"); +#endif + /// + /// 脚本引擎目录 + /// + public string ScriptEngineDirectory => Environment.GetEnvironmentVariable("cscs_exe_dir"); + + /// + /// 正在执行的脚本的文件名 + /// + public string ScriptEngineEntryScriptAssembly => Environment.GetEnvironmentVariable("EntryScriptAssembly"); + + public ScriptEngine() + { + // 使用CodeDom编译引擎 + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + // 开启缓存系统 + CSScript.CacheEnabled = true; + // 使用正在内存中的程序集以加快编译速度 + CSScript.GlobalSettings.InMemoryAssembly = true; + // 再另一个引擎验证未完成之前脚本引擎不启动时间戳验证和脚本编译 + CSScript.GlobalSettings.ConcurrencyControl = ConcurrencyControl.Standard; + CSSEnvironment.SetScriptTempDir(Path.Combine(Environment.CurrentDirectory, "scriptCache")); + } + + /// + /// 编译单文件 + /// + /// 文件名 + public void CompileFile(string file) + { + if (!File.Exists(file)) + { + return; + } + + CSScript.CompileFile(file); + } + + /// + /// 编译多个文件 + /// + /// 文件列表 + public void CompileFiles(string[] files) + { + if (files == null || files.Length <= 0) + { + return; + } + + CSScript.CompileFiles(files); + } + + /// + /// 加载多个文件 + /// + /// 文件列表 + public void LoadFiles(string[] files) + { + if (files == null || files.Length <= 0) + { + return; + } + + CSScript.LoadFiles(files); + } + + /// + /// 加载文件 + /// + /// 文件名 + /// 程序集 + public Assembly LoadFile(string file) + { + return !File.Exists(file) ? null : CSScript.LoadFile(file); + } + + /// + /// 计算后执行文件 + /// + /// + /// + public IBehaviour EvaluatorLoadFile(string file) + { + return !File.Exists(file) ? null : CSScript.Evaluator.LoadFile(file); + } + } +} \ No newline at end of file diff --git a/CLEngine.ScriptEngine/Scripting.Extensions.cs b/CLEngine.ScriptEngine/Scripting.Extensions.cs new file mode 100644 index 0000000..0705364 --- /dev/null +++ b/CLEngine.ScriptEngine/Scripting.Extensions.cs @@ -0,0 +1,295 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.Remoting.Lifetime; +using System.Threading; +using System.Threading.Tasks; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios requiring asynchronous script execution as well as unloading the +// scripts being executed. +// AsyncSamples +// Samples demonstrate the use of Async and Await mechanism available in C# 5 and higher. Note that the async method extensions +// cover the complete set of CSScript.Evaluator methods. +// +// UnloadingSamples +// Samples demonstrate the use of temporary AppDoamain for loading and executing dynamic C# code (script). It is the +// only mechanism available for unloading dynamically loaded assemblies. This is a well known CLR design limitation that leads to +// memory leaks if the assembly/script loaded in the caller AppDomain. The problem affects all C# script engines (e.g. Roslyn, CodeDom) +// and it cannot be solved by the engine itself thus CS-Script provides a work around in form of the MethodExtensions for the +// CSScript.Evaluator methods that are compatible with the unloading mechanism. +// +// Nevertheless you should try to avoid using remote AppDoamain unless you have to. It is very heavy and also imposes the serialization +// constrains. +// +// All samples rely on the compiler agnostic CSScript.Evaluator API. +namespace CSScriptEvaluatorExtensions +{ + public class HostApp + { + public static void Test() + { + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing asynchronous API"); + Console.WriteLine("---------------------------------------------"); + new AsyncSamples().RunAll(); + Thread.Sleep(2000); + Console.WriteLine("\nPress 'Enter' to run uloading samples..."); + Console.ReadLine(); + Console.WriteLine("---------------------------------------------"); + Console.WriteLine("Testing unloading API"); + Console.WriteLine("---------------------------------------------"); + new UnloadingSamples().RunAll(); + } + + class AsyncSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name); }; + + run(LoadDelegateAsync, "Start of " + nameof(LoadDelegateAsync)); + run(LoadMethodAsync, "Start of " + nameof(LoadMethodAsync)); + run(LoadCodeAsync, "Start of " + nameof(LoadCodeAsync)); + run(CreateDelegateAsync, "Start of " + nameof(CreateDelegateAsync)); + run(CompileCodeAsync, "Start of " + nameof(CompileCodeAsync)); + run(RemoteAsynch, "Start of " + nameof(RemoteAsynch)); + } + + async void LoadDelegateAsync() + { + var product = await CSScript.Evaluator + .LoadDelegateAsync>( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadDelegateAsync), product(4, 2)); + } + + public async void LoadMethodAsync() + { + dynamic script = await CSScript.Evaluator + .LoadMethodAsync(@"public int Sum(int a, int b) + { + return a + b; + } + public int Div(int a, int b) + { + return a/b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadMethodAsync), script.Div(15, 3)); + } + + public async void LoadCodeAsync() + { + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = await CSScript.Evaluator + .LoadCodeAsync( + @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(LoadCodeAsync), calc.Sum(1, 2)); + } + + public async void CreateDelegateAsync() + { + var product = await CSScript.Evaluator + .CreateDelegateAsync( + @"int Product(int a, int b) + { + return a * b; + }"); + + Console.WriteLine(" End of {0}: {1}", nameof(CreateDelegateAsync), product(15, 3)); + } + + public async void CompileCodeAsync() + { + Assembly script = await CSScript.Evaluator + .CompileCodeAsync(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + dynamic calc = script.CreateObject("*"); + + Console.WriteLine(" End of {0}: {1}", nameof(CompileCodeAsync), calc.Sum(15, 3)); + } + + public async void RemoteAsynch() + { + var sum = await Task.Run(() => + CSScript.Evaluator + .CreateDelegateRemotely( + @"int Sum(int a, int b) + { + return a+b; + }") + ); + Console.WriteLine(" End of {0}: {1}", nameof(RemoteAsynch), sum(1, 2)); + + sum.UnloadOwnerDomain(); + } + } + + class UnloadingSamples + { + public void RunAll() + { + CreateDelegateRemotely(); + LoadMethodRemotely(); + LoadCodeRemotely(); + LoadCodeRemotelyWithInterface(); + } + + public void CreateDelegateRemotely() + { + var sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + Console.WriteLine("{0}: {1}", nameof(CreateDelegateRemotely), sum(15, 3)); + + sum.UnloadOwnerDomain(); + } + + public void LoadCodeRemotely() + { + // Class Calc doesn't implement ICals interface. Thus the compiled object cannot be typecasted into + // the interface and Evaluator will emit duck-typed assembly instead. + // But Mono and Roslyn build file-less assemblies, meaning that they cannot be used to build + // duck-typed proxies and CodeDomEvaluator needs to be used explicitly. + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.CodeDomEvaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject + { + object t; + public int Sum(int a, int b) + { + t = new Test(); + return a+b; + } + } + + class Test + { + ~Test() + { + Console.WriteLine(""Domain is unloaded: ~Test()""); + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadCodeRemotelyWithInterface() + { + // Note class Calc also inherits from MarshalByRefObject. This is required for all object that + // are passed between AppDomain: they must inherit from MarshalByRefObject or be serializable. + var script = CSScript.Evaluator + .LoadCodeRemotely( + @"using System; + public class Calc : MarshalByRefObject, CSScriptEvaluatorExtensions.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + } + "); + + Console.WriteLine("{0}: {1}", nameof(LoadCodeRemotelyWithInterface), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + public void LoadMethodRemotely() + { + // LoadMethodRemotely is essentially the same as LoadCodeRemotely. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var script = CSScript.Evaluator + .LoadMethodRemotely( + @"public int Sum(int a, int b) + { + return a+b; + } + public int Sub(int a, int b) + { + return a-b; + }"); + + Console.WriteLine("{0}: {1}", nameof(LoadMethodRemotely), script.Sum(15, 3)); + + script.UnloadOwnerDomain(); + } + + MethodDelegate sum; + ClientSponsor sumSponsor; + + public void KeepRemoteObjectAlive() + { + sum = CSScript.Evaluator + .CreateDelegateRemotely(@"int Sum(int a, int b) + { + return a+b; + }"); + + //Normally remote objects are disposed if they are not accessed withing a default timeout period. + //It is not even enough to keep transparent proxies or their wrappers (e.g. 'sum') referenced. + //To prevent GC collection in the remote domain use .NET ClientSponsor mechanism as below. + sumSponsor = sum.ExtendLifeFromMinutes(30); + } + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public interface IFullCalc + { + int Sum(int a, int b); + + int Sub(int a, int b); + } +} \ No newline at end of file diff --git a/CLEngine.ScriptEngine/Scripting.evaluator.cs b/CLEngine.ScriptEngine/Scripting.evaluator.cs new file mode 100644 index 0000000..0e19577 --- /dev/null +++ b/CLEngine.ScriptEngine/Scripting.evaluator.cs @@ -0,0 +1,345 @@ +using CSScriptLibrary; +using System; +using System.Diagnostics; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Evaluator interface (API). +// This API is a unified generic interface allowing dynamic switch of the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// Apart from Evaluator (compiler agnostic) API CS-Script offers alternative hosting model: CS-Script Native, +// which relies solely on CodeDom compiler. CS-Script Native offers some features that are not available with CS-Script Evaluator +// (e.g. script unloading). +// +// The choice of the underlying compiling engine (e.g. Mono vs CodeDom) when using CS-Script Evaluator is always dictated by the +// specifics of the hosting scenario. Thanks to in-process compiler hosting, Mono and Roslyn demonstrate much better compiling +// performance comparing to CodeDom engine. However they don't allow script debugging and caching easily supported with CodeDom. +// Mono and particularly Roslyn also leas create more memory pressure due to the higher volume of the temp assemblies loaded into +// the hosting AppDomain. Roslyn (at least CSharp.Scripting-v1.2.0.0) also has very high initial loading overhead up to 4 seconds. +// +// One of the possible approaches would be to use EvaluatorEngine.CodeDom during the active development and later on switch to Mono/Roslyn. + +namespace CSScriptEvaluatorApi +{ + public class HostApp + { + public static void Test() + { + // Just in case clear AlternativeCompiler so it is not set to Roslyn or anything else by + // the CS-Script installed (if any) on the host OS + CSScript.GlobalSettings.UseAlternativeCompiler = null; + + var samples = new EvaluatorSamples(); + + Console.WriteLine("Testing compiling services"); + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + samples.RunAll(); + + Console.WriteLine("---------------------------------------------"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + Console.WriteLine(CSScript.Evaluator.GetType().Name + "..."); + + samples.RunAll(); + + //samples.DebugTest(); //uncomment if want to fire an assertion during the script execution + + //Profile(); //uncomment if want to test performance of the engines + } + + class EvaluatorSamples + { + public void RunAll() + { + Action run = (action, name) => { action(); Console.WriteLine(name + " - OK"); }; + + run(CompileMethod_Instance, nameof(CompileMethod_Instance)); + run(CompileMethod_Static, nameof(CompileMethod_Static)); + run(CreateDelegate, nameof(CreateDelegate)); + run(LoadDelegate, nameof(LoadDelegate)); + run(LoadCode, nameof(LoadCode)); + run(LoadMethod, nameof(LoadMethod)); + run(LoadMethodWithInterface, nameof(LoadMethodWithInterface)); + run(LoadCode_WithInterface, nameof(LoadCode_WithInterface)); + run(LoadCode_WithDuckTypedInterface, nameof(LoadCode_WithDuckTypedInterface)); + } + + public void CompileMethod_Instance() + { + // 1- CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.Evaluator + .CompileMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public void CompileMethod_Static() + { + // 1 - CompileMethod wraps method into a class definition and returns compiled assembly + // 2 - GetStaticMethod returns duck-typed delegate that accepts 'params object[]' arguments + // Note: GetStaticMethodWithArgs can be replaced with a more convenient/shorter version + // that takes the object instead of the Type and then queries objects type internally: + // "GetStaticMethod("*.Test", data)" + + var test = CSScript.Evaluator + .CompileMethod(@"using CSScriptEvaluatorApi; + static void Test(InputData data) + { + data.Index = GetIndex(); + } + static int GetIndex() + { + return Environment.TickCount; + }") + .GetStaticMethodWithArgs("*.Test", typeof(InputData)); + + var data = new InputData(); + test(data); + } + + public void CreateDelegate() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sqr = CSScript.Evaluator + .CreateDelegate(@"int Sqr(int a) + { + return a * a; + }"); + + var r = sqr(3); + } + + public void LoadDelegate() + { + // Wraps method into a class definition, loads the compiled assembly + // and returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + var product = CSScript.Evaluator + .LoadDelegate>( + @"int Product(int a, int b) + { + return a * b; + }"); + + int result = product(3, 2); + } + + public void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethod() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. + // 'public' is optional as it will be injected if the code doesn't start with it. + dynamic script = CSScript.Evaluator + .LoadMethod(@"using System; + public int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadMethodWithInterface() + { + // LoadMethod compiles code and returns instance of a first class + // in the compiled assembly. + // LoadMethod is essentially the same as LoadCode. It just deals not with the + // whole class definition but a single method(s) only. And the rest of the class definition is + // added automatically by CS-Script. The auto-generated class declaration also indicates + // that the class implements ICalc interface. Meaning that it will trigger compile error + // if the set of methods in the script code doesn't implement all interface members. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.Evaluator + .LoadMethod( + @"int Sum(int a, int b) + { + return a+b; + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2 - The script class implements host app interface so the returned object can be type casted into it + + var script = (ICalc)CSScript.Evaluator + .LoadCode(@"using System; + public class Script : CSScriptEvaluatorApi.ICalc + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void LoadCode_WithDuckTypedInterface() + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc script = CSScript.MonoEvaluator + .LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + int result = script.Sum(1, 2); + } + + public void PerformanceTest(int count = -1) + { + var code = @"int Sqr(int a) + { + return a * a; + }"; + + if (count != -1) + code += "//" + count; //this unique extra code comment ensures the code to be compiled cannot be cached + + dynamic script = CSScript.Evaluator + .CompileMethod(code) + .CreateObject("*"); + + var r = script.Sqr(3); + } + + public void DebugTest() + { + //pops up an assertion dialog + + CSScript.EvaluatorConfig.DebugBuild = true; + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + + dynamic script = CSScript.Evaluator + .LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }"); + + var r = script.Sum(3, 4); + } + } + + public static void Profile() + { + var sw = new Stopwatch(); + var samples = new EvaluatorSamples(); + var count = 20; + var inxed = 0; + bool preventCaching = false; + + Action run = () => + { + sw.Restart(); + for (int i = 0; i < count; i++) + if (preventCaching) + samples.PerformanceTest(inxed++); + else + samples.PerformanceTest(); + + Console.WriteLine(CSScript.Evaluator.GetType().Name + ": " + sw.ElapsedMilliseconds); + }; + + Action runAll = () => + { + Console.WriteLine("\n---------------------------------------------"); + Console.WriteLine($"Caching enabled: {!preventCaching}\n"); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Mono; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.CodeDom; + run(); + + CSScript.EvaluatorConfig.Engine = EvaluatorEngine.Roslyn; + run(); + }; + + RoslynEvaluator.LoadCompilers(); //Roslyn is extremely heavy so exclude startup time from profiling + + Console.WriteLine("Testing performance"); + + preventCaching = true; + runAll(); + + preventCaching = false; + runAll(); + } + } + + public interface ICalc + { + int Sum(int a, int b); + } + + public class InputData + { + public int Index = 0; + } +} \ No newline at end of file diff --git a/CLEngine.ScriptEngine/Scripting.native.cs b/CLEngine.ScriptEngine/Scripting.native.cs new file mode 100644 index 0000000..37c7ef2 --- /dev/null +++ b/CLEngine.ScriptEngine/Scripting.native.cs @@ -0,0 +1,354 @@ +using CSScriptLibrary; +using System; +using System.Linq; +using System.IO; +using csscript; +using System.CodeDom.Compiler; + +// Read in more details about all aspects of CS-Script hosting in applications +// here: http://www.csscript.net/help/Script_hosting_guideline_.html +// +// This file contains samples for the script hosting scenarios relying on CS-Script Native interface (API). +// This API is a compiler specific interface, which relies solely on CodeDom compiler. In most of the cases +// CS-Script Native model is the most flexible and natural choice +// +// Apart from Native API CS-Script offers alternative hosting model: CS-Script Evaluator, which provides +// a unified generic interface allowing dynamic switch the underlying compiling services (Mono, Roslyn, CodeDom) +// without the need for changing the hosting code. +// +// The Native interface is the original API that was designed to take maximum advantage of the dynamic C# code +// execution with CodeDom. The original implementation of this API was developed even before any compiler-as-service +// solution became available. Being based solely on CodeDOM the API doesn't utilize neither Mono nor Roslyn +// scripting solutions. Despite that CS-Script Native is the most mature, powerful and flexible API available with CS-Script. +// +// Native interface allows some unique features that are not available with CS-Script Evaluator: +// - Debugging scripts +// - Script caching +// - Script unloading + +namespace CSScriptNativeApi +{ + public class HostApp + { + public static void Test() + { + var host = new HostApp(); + host.Log("Testing compiling services CS-Script Native API"); + Console.WriteLine("---------------------------------------------"); + + CodeDomSamples.LoadMethod_Instance(); + CodeDomSamples.LoadMethod_Static(); + CodeDomSamples.LoadDelegate(); + CodeDomSamples.CreateAction(); + CodeDomSamples.CreateFunc(); + CodeDomSamples.LoadCode(); + CodeDomSamples.LoadCode_WithInterface(host); + CodeDomSamples.LoadCode_WithDuckTypedInterface(host); + CodeDomSamples.ExecuteAndUnload(); + //CodeDomSamples.DebugTest(); //uncomment if want to fire an assertion during the script execution + } + + public class CodeDomSamples + { + public static void LoadMethod_Instance() + { + // 1- LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - CreateObject creates instance of a first class in the assembly + + dynamic script = CSScript.LoadMethod(@"int Sqr(int data) + { + return data * data; + }") + .CreateObject("*"); + + var result = script.Sqr(7); + } + + public static void LoadMethod_Static() + { + // 1 - LoadMethod wraps method into a class definition, compiles it and returns loaded assembly + // 2 - GetStaticMethod returns first found static method as a duck-typed delegate that + // accepts 'params object[]' arguments + // + // Note: you can use GetStaticMethodWithArgs for higher precision method search: GetStaticMethodWithArgs("*.SayHello", typeof(string)); + var sayHello = CSScript.LoadMethod(@"static void SayHello(string greeting) + { + Console.WriteLine(greeting); + }") + .GetStaticMethod(); + + sayHello("Hello World!"); + } + + public static void LoadDelegate() + { + // LoadDelegate wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns the method delegate for the method, which matches the delegate specified + // as the type parameter of LoadDelegate + + // The 'using System;' is optional; it demonstrates how to specify 'using' in the method-only syntax + + var sayHello = CSScript.LoadDelegate>( + @"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateAction() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // without any specific return type. + + var sayHello = CSScript.CreateAction(@"void SayHello(string greeting) + { + Console.WriteLine(greeting); + }"); + + sayHello("Hello World!"); + } + + public static void CreateFunc() + { + // Wraps method into a class definition, compiles it and loads the compiled assembly. + // It returns duck-typed delegate. A delegate with 'params object[]' arguments and + // int as a return type. + + var Sqr = CSScript.CreateFunc(@"int Sqr(int a) + { + return a * a; + }"); + int r = Sqr(3); + } + + public static void LoadCode() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + dynamic script = CSScript.LoadCode(@"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + + public static void LoadCodeWithConfig() + { + // LoadCode compiles code and returns instance of a first class + // in the compiled assembly + + string file = Path.GetTempFileName(); + try + { + File.WriteAllText(file, @"using System; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + var settings = new Settings(); + //settings = null; // set to null to foll back to defaults + + dynamic script = CSScript.LoadWithConfig(file, null, false, settings, "/define:TEST") + .CreateObject("*"); + + int result = script.Sum(1, 2); + } + finally + { + if (File.Exists(file)) + File.Delete(file); + } + } + + public static void LoadCode_WithInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly. + // 2 - The script class implements host app interface so the returned object can be type casted into it. + // 3 - In this sample host object is passed into script routine. + + var calc = (ICalc) CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script : ICalc + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*"); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void LoadCode_WithDuckTypedInterface(HostApp host) + { + // 1 - LoadCode compiles code and returns instance of a first class in the compiled assembly + // 2- The script class doesn't implement host app interface but it can still be aligned to + // one as long at it implements the interface members + // 3 - In this sample host object is passed into script routine. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + ICalc calc = CSScript.LoadCode(@"using CSScriptNativeApi; + public class Script + { + public int Sum(int a, int b) + { + if(Host != null) + Host.Log(""Sum is invoked""); + return a + b; + } + + public HostApp Host { get; set; } + }") + .CreateObject("*") + .AlignToInterface(); + calc.Host = host; + int result = calc.Sum(1, 2); + } + + public static void ExecuteAndUnload() + { + // The script will be loaded into a temporary AppDomain and unloaded after the execution. + + // Note: remote execution is a subject of some restrictions associated with the nature of the + // CLR cross-AppDomain interaction model: + // * the script class must be serializable or derived from MarshalByRefObject. + // + // * any object (call arguments, return objects) that crosses ApPDomain boundaries + // must be serializable or derived from MarshalByRefObject. + // + // * long living script class instances may get disposed in remote domain even if they are + // being referenced in the current AppDomain. You need to use the usual .NET techniques + // to prevent that. See LifetimeManagement.cs sample for details. + + //This use-case uses Interface Alignment and this requires all assemblies involved to have + //non-empty Assembly.Location + CSScript.GlobalSettings.InMemoryAssembly = false; + + var code = @"using System; + public class Script : MarshalByRefObject + { + public void Hello(string greeting) + { + Console.WriteLine(greeting); + } + }"; + + //Note: usage of helper.CreateAndAlignToInterface("Script") is also acceptable + using (var helper = new AsmHelper(CSScript.CompileCode(code), null, deleteOnExit: true)) + { + IScript script = helper.CreateAndAlignToInterface("*"); + script.Hello("Hi there..."); + } + //from this point AsmHelper is disposed and the temp AppDomain is unloaded + } + + public static void DebugTest() + { + //pops up an assertion dialog + dynamic script = CSScript.LoadCode(@"using System; + using System.Diagnostics; + public class Script + { + public int Sum(int a, int b) + { + Debug.Assert(false,""Testing CS-Script debugging...""); + return a+b; + } + }", null, debugBuild: true).CreateObject("*"); + + int result = script.Sum(1, 2); + } + } + + public void Log(string message) + { + Console.WriteLine(message); + } + } + + public interface IScript + { + void Hello(string greeting); + } + + public interface ICalc + { + HostApp Host { get; set; } + + int Sum(int a, int b); + } + + public class Samples + { + static public void CompilingHistory() + { + string script = Path.GetTempFileName(); + string scriptAsm = script + ".dll"; + CSScript.KeepCompilingHistory = true; + + try + { + File.WriteAllText(script, @"using System; + using System.Windows.Forms; + public class Script + { + public int Sum(int a, int b) + { + return a+b; + } + }"); + + + + CSScript.CompileFile(script, scriptAsm, false, null); + + CompilingInfo info = CSScript.CompilingHistory + .Values + .FirstOrDefault(item => item.ScriptFile == script); + if (info != null) + { + Console.WriteLine("Script: " + info.ScriptFile); + + Console.WriteLine("Referenced assemblies:"); + foreach (string asm in info.Input.ReferencedAssemblies) + Console.WriteLine(asm); + + if (info.Result.Errors.HasErrors) + { + foreach (CompilerError err in info.Result.Errors) + if (!err.IsWarning) + Console.WriteLine("Error: " + err.ErrorText); + } + } + + CSScript.CompilingHistory.Clear(); + + } + finally + { + CSScript.KeepCompilingHistory = false; + } + } + } +} \ No newline at end of file diff --git a/CLEngine.ScriptEngine/app.config b/CLEngine.ScriptEngine/app.config new file mode 100644 index 0000000..6ef8473 --- /dev/null +++ b/CLEngine.ScriptEngine/app.config @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CLEngine.ScriptEngine/packages.config b/CLEngine.ScriptEngine/packages.config new file mode 100644 index 0000000..b6cfb3b --- /dev/null +++ b/CLEngine.ScriptEngine/packages.config @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CLEngine/CGame.cs b/CLEngine/CGame.cs index bf708b7..8def052 100644 --- a/CLEngine/CGame.cs +++ b/CLEngine/CGame.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Text; using CLEditor.Core.Diagnostics; using CLEngine.Properties; @@ -15,11 +16,32 @@ namespace CLEngine /// public class CGame : Game { + /// + /// 日志引擎 + /// private readonly LogListener logListener; - private readonly Lua Lua; - private readonly object[] MainObject; + /// + /// Lua脚本引擎 + /// private readonly LuaTable MainTable; + + /// + /// CSharp程序集 + /// + private readonly Assembly ScriptAssembly; + /// + /// CSharp脚本行为 + /// + private readonly IBehaviour Behaviour; + + private readonly LuaFunction LuaInitFunc; + private readonly LuaFunction LuaUpdateFunc; + private readonly LuaFunction LuaDrawFunc; + private readonly LuaFunction LuaLoadContentFunc; + private readonly LuaFunction LuaUnLoadContentFunc; + private const string MainCoreLuaFile = "core.lua"; + private const string MainCoreCSFile = "Core.cs"; public CGame() { @@ -30,33 +52,120 @@ namespace CLEngine GlobalLogger.GlobalMessageLogged += logListener; } - Lua = new Lua(); - Lua.LoadCLRPackage(); - if (!File.Exists(MainCoreLuaFile)) + var lua = new Lua(); + lua.LoadCLRPackage(); + + NoExistAndCreateFile(MainCoreLuaFile, Resources.LuaCore); + var mainObject = lua.DoFile(MainCoreLuaFile.GetScriptPath()); + MainTable = mainObject[0] as LuaTable ?? throw new ArgumentException("脚本发生严重错误!"); + + var initIndex = FindKeyIndex("init"); + LuaInitFunc = FindValueIndex(initIndex); + var drawIndex = FindKeyIndex("draw"); + LuaDrawFunc = FindValueIndex(drawIndex); + var updateIndex = FindKeyIndex("update"); + LuaUpdateFunc = FindValueIndex(updateIndex); + var loadContentIndex = FindKeyIndex("loadContent"); + LuaLoadContentFunc = FindValueIndex(loadContentIndex); + var unloadContentIndex = FindKeyIndex("unLoadContent"); + LuaUnLoadContentFunc = FindValueIndex(unloadContentIndex); + + NoExistAndCreateFile(MainCoreCSFile, Resources.CSCore); + var scriptEngine = new ScriptEngine.ScriptEngine(); + scriptEngine.CompileFile(MainCoreCSFile.GetScriptPath()); + Behaviour = scriptEngine.EvaluatorLoadFile(MainCoreCSFile.GetScriptPath()); + } + + /// + /// 不存在则通过模板创建脚本文件 + /// + private void NoExistAndCreateFile(string fileName, byte[] resource) + { + var scriptName = Path.Combine("scripts", fileName); + var scriptDir = Path.GetDirectoryName(scriptName); + if (File.Exists(scriptName)) return; + + if (!string.IsNullOrEmpty(scriptDir) && !Directory.Exists(scriptDir)) { - using (var coreLuaStream = File.Create(MainCoreLuaFile)) - using (var streamWriter = new StreamWriter(coreLuaStream, Encoding.UTF8)) - { - streamWriter.Write(Encoding.UTF8.GetString(Resources.core)); - } + Directory.CreateDirectory(scriptDir); } - MainObject = Lua.DoFile(MainCoreLuaFile); - MainTable = MainObject[0] as LuaTable ?? throw new ArgumentException("脚本发生严重错误!"); + + using (var coreStream = File.Create(scriptName)) + using (var streamWriter = new StreamWriter(coreStream, Encoding.UTF8)) + { + streamWriter.Write(Encoding.UTF8.GetString(resource)); + } } + /// + /// 游戏初始化 + /// protected override void Initialize() { + Window.Title = "CL游戏 - 开启新世界"; + base.Initialize(); - var index = FindKeyIndex("init"); - CallValueIndex(index); + LuaInitFunc.Call(); + Behaviour.Initialize(); + } + + /// + /// 游戏绘制 + /// + /// + protected override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + + LuaDrawFunc.Call(gameTime); + Behaviour.Draw(gameTime); } + /// + /// 游戏更新 + /// + /// + protected override void Update(GameTime gameTime) + { + base.Update(gameTime); + + LuaUpdateFunc.Call(gameTime); + Behaviour.Update(gameTime); + } + + /// + /// 加载资源 + /// + protected override void LoadContent() + { + base.LoadContent(); + + LuaLoadContentFunc.Call(); + Behaviour.LoadContent(); + } + + /// + /// 卸载资源 + /// + protected override void UnloadContent() + { + base.UnloadContent(); + + LuaUnLoadContentFunc.Call(); + Behaviour.UnLoadContent(); + } + + /// + /// 根据方法名寻找序列 + /// + /// + /// private int FindKeyIndex(string functionName) { var index = -1; - var keyCollection = MainTable.Keys as Dictionary.KeyCollection; + if (!(MainTable.Keys is Dictionary.KeyCollection keyCollection)) return index; foreach (var key in keyCollection) { index++; @@ -69,20 +178,31 @@ namespace CLEngine return index; } - private void CallValueIndex(int index) + /// + /// 根据序列寻找方法并执行 + /// + /// + private LuaFunction FindValueIndex(int index) { var step = 0; - var valueCollection = MainTable.Values as Dictionary.ValueCollection; + if (!(MainTable.Values is Dictionary.ValueCollection valueCollection)) return null; foreach (var value in valueCollection) { if (step == index) { - (value as LuaFunction)?.Call(); + return value as LuaFunction; } + step++; } + + return null; } + /// + /// 获取日志监听器 + /// + /// 日志监听器 protected virtual LogListener GetLogListener() { return new ConsoleLogListener(); diff --git a/CLEngine/CLEngine.csproj b/CLEngine/CLEngine.csproj index 9304496..06d11f3 100644 --- a/CLEngine/CLEngine.csproj +++ b/CLEngine/CLEngine.csproj @@ -9,8 +9,9 @@ Properties CLEngine CLEngine - v4.5 + v4.6.1 512 + true @@ -50,12 +51,14 @@ + True True Resources.resx + @@ -66,10 +69,15 @@ {A9459A99-39D8-480B-BF14-A7687ECE7DB1} CLEditor.Core + + {de6b0459-39ba-49b6-8b63-1bb3a8b5105b} + CLEngine.ScriptEngine + ResXFileCodeGenerator + Designer Resources.Designer.cs diff --git a/CLEngine/GameExtensions.cs b/CLEngine/GameExtensions.cs new file mode 100644 index 0000000..a3bafb2 --- /dev/null +++ b/CLEngine/GameExtensions.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace CLEngine +{ + /// + /// 游戏帮助类 + /// + public static class GameExtensions + { + /// + /// 获取脚本路径 + /// + /// 文件名 + /// + public static string GetScriptPath(this string fileName) + { + return Path.Combine("scripts", fileName); + } + } +} \ No newline at end of file diff --git a/CLEngine/Properties/Resources.Designer.cs b/CLEngine/Properties/Resources.Designer.cs index ac5f185..ff8b042 100644 --- a/CLEngine/Properties/Resources.Designer.cs +++ b/CLEngine/Properties/Resources.Designer.cs @@ -63,9 +63,19 @@ namespace CLEngine.Properties { /// /// 查找 System.Byte[] 类型的本地化资源。 /// - internal static byte[] core { + internal static byte[] CSCore { get { - object obj = ResourceManager.GetObject("core", resourceCulture); + object obj = ResourceManager.GetObject("CSCore", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] LuaCore { + get { + object obj = ResourceManager.GetObject("LuaCore", resourceCulture); return ((byte[])(obj)); } } diff --git a/CLEngine/Properties/Resources.resx b/CLEngine/Properties/Resources.resx index 05a58e1..f1c2d19 100644 --- a/CLEngine/Properties/Resources.resx +++ b/CLEngine/Properties/Resources.resx @@ -118,7 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + ..\Templates\Core.cs;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Templates\core.lua;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 \ No newline at end of file diff --git a/CLEngine/Templates/Core.cs b/CLEngine/Templates/Core.cs new file mode 100644 index 0000000..2fc2bf1 --- /dev/null +++ b/CLEngine/Templates/Core.cs @@ -0,0 +1,52 @@ +using System; +using CLEngine; +using Microsoft.Xna.Framework; + +/// +/// CSharp脚本主入口 +/// +public class Core : IBehaviour +{ + /// + /// 游戏初始化 + /// + public void Initialize() + { + + } + + /// + /// 游戏逻辑更新 + /// + /// 循环时间 + public void Update(GameTime gameTime) + { + + } + + /// + /// 游戏帧更新 + /// 游戏绘制逻辑 + /// + /// 循环时间 + public void Draw(GameTime gameTime) + { + + } + + /// + /// 加载资源 + /// + public void LoadContent() + { + + } + + /// + /// 卸载资源 + /// + public void UnLoadContent() + { + + } +} \ No newline at end of file diff --git a/CLEngine/Templates/core.lua b/CLEngine/Templates/core.lua index 565356d..476ce51 100644 --- a/CLEngine/Templates/core.lua +++ b/CLEngine/Templates/core.lua @@ -1,14 +1,36 @@ +-- Lua脚本主入口点 local self = {} +-- 游戏初始化 +function self:init() + +end + --[[ - 游戏初始化 + * 游戏逻辑更新 + * gameTime: 游戏循环时间 ]] -function self:init() +function self:update(gameTime) + +end + +--[[ + * 游戏帧更新 + * 图形绘制更新 + * gameTime: 游戏循环时间 +]] +function self:draw(gameTime) + +end + +-- 游戏加载资源 +function self:loadContent() end -function self:update() +-- 游戏卸载资源 +function self:unLoadContent() end -return self \ No newline at end of file +return self -- 必须返回self \ No newline at end of file diff --git a/Game.Windows/Game.Windows.csproj b/Game.Windows/Game.Windows.csproj index 86e7811..a9cbfc0 100644 --- a/Game.Windows/Game.Windows.csproj +++ b/Game.Windows/Game.Windows.csproj @@ -1,5 +1,5 @@  - + Debug @@ -13,7 +13,8 @@ Game.Windows 512 Windows - v4.5 + v4.6.1 + x86 @@ -57,6 +58,7 @@ + diff --git a/Game.Windows/app.config b/Game.Windows/app.config new file mode 100644 index 0000000..3dbff35 --- /dev/null +++ b/Game.Windows/app.config @@ -0,0 +1,3 @@ + + + -- Gitee