Magnet.Core 1.0.5

There is a newer version of this package available.
See the version list below for details.
dotnet add package Magnet.Core --version 1.0.5                
NuGet\Install-Package Magnet.Core -Version 1.0.5                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Magnet.Core" Version="1.0.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Magnet.Core --version 1.0.5                
#r "nuget: Magnet.Core, 1.0.5"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Magnet.Core as a Cake Addin
#addin nuget:?package=Magnet.Core&version=1.0.5

// Install Magnet.Core as a Cake Tool
#tool nuget:?package=Magnet.Core&version=1.0.5                

<p align="center" > <span>Magnet</span> </p> <div align=center><img src="icon.png"></div>

What is Magnet?


我原本想做的仅仅是一款游戏的服务器脚本引擎,从一开始想做一个强类型的类TypeScript的脚本引擎<br/> 后面从词法分析到语法分析,做完之后发现编译器后端的复杂程度过高。 即使编译器后端开发完成性能和扩展性也很难达到我的要求<br/> 所以尝试使用Roslyn引擎来定制一款C#脚本引擎,使用Roslyn的好处就是完全不用担心他的性能和扩展性,Roslyn提供了完整的语法树API<br/> 和强大的编译选项搭配上C#的语法和特性完全可以实现一款满足我需求的脚本。<br/> 之所以取名叫做Magnet就是希望他可以像磁铁一样吸到宿主的Project上可以随时取下来。<br/> 当然它不仅能用做游戏的服务器逻辑处理,它可以用作任何需要它的地方。<br/> 当前处于开发阶段,所以部分API可能会有改动, 例子可能导致编译失败,请查看例子源码自行修改。


C#语法 高性能 可扩展 可调试 可卸载 多State 安全性


💥脚本基础功能

支持仅编译、仅加载、从脚本编译加载模式。

// 脚本名称
options.WithName(name);

// 脚本是否支持异步
options.WithAllowAsync(false);

// 脚本是否支持不安全代码
options.WithAllowUnsafe(true);

// 使用默认的编译抑制诊断
options.UseDefaultSuppressDiagnostics();

// 脚本程序集上下文依赖程序集加载Hook
options.WithAssemblyLoadCallback(AssemblyLoad);

💥脚本编译输入与输出

支持仅编译、仅加载、从脚本编译加载模式。

// #1 仅编译,可输出
options.WithCompileKind(CompileKind.Compile);
options.WithOutPutFile("sample.dll");

// #2 从程序集文件加载
options.WithCompileKind(CompileKind.LoadAssembly);
options.WithScanDirectory("./");
options.WithAssemblyFileName("sample.dll");

// #3 从脚本文件编译并加载
options.WithCompileKind(CompileKind.CompileAndLoadAssembly);
options.WithScanDirectory("../../../../Scripts");

💥脚本编译优化与设置


// 调试模式 启用脚本内置debugger()函数
options.WithDebug(true);

// 调试模式 不启用脚本内置debugger()函数
options.WithDebug(false);

// 发布模式 编译优化
//options.WithRelease();

💥添加脚本的程序集引用

// 添加 System.Threading 程序集的引用
options.AddReferences<Thread>();

// 添加 System.Threading 程序集的引用
options.AddReferences(typeof(Thread));

// 添加 System.Threading 程序集的引用
options.AddReferences("System.Threading.dll");

💥带有编译检查的类型与命名空间禁用

如果脚本中使用了被禁用的类型或命名空间后,将会触发编译失败。 <br> ICompileResult.Diagnostics 内会包含诊断错误 同时 ICompileResult.Success = false

//禁用类型
options.DisableType(typeof(Task));

// 禁用泛类型的严格类型
options.DisableType("System.Collections.Generic.List<string>");
options.DisableType(typeof(List<String>));

// 禁用范类型的基础类型
options.DisableType("System.Collections.Generic.List");
options.DisableGenericBaseType(typeof(List<>));

💥对象类型替换器

在编译脚本阶段,将语法树上的类型替换为新的类型。<br> 如果新类型的成员对象签名与原类型的不一致可能会抛出异常。

// 替换类型 将脚本内使用的Task 替换为MyTask
options.AddReplaceType(typeof(Task), typeof(MyTask));

// 脚本类型重写器(加强版的AddReplaceType)
options.WithTypeRewriter(new TypeRewriter());

💥功能扩展分析器

分析器实现了以下三个分析器接口,宿主可以通过分析器实现定制功能开发

完整例子查看 Magnet.Examples 的 App.Core.Timer.TimerProvider

分析器 描述 触发时机
IAssemblyAnalyzer 脚本程序集分析器 脚本程序集加载完毕后
ITypeAnalyzer Script类型分析器 脚本程序集加载完毕后
IInstanceAsalyzer 和脚本实例分析器 脚本State创建时
var timerProvider = new TimerProvider();

// 增加一个分析器
options.AddAnalyzer(timerProvider);


public class TimerProvider : ITypeAnalyzer
{

    void ITypeAnalyzer.DefineType(Type type)
    {
    }

    void IAnalyzer.Connect(MagnetScript magnet)
    {

    }

    void IAnalyzer.Disconnect(MagnetScript magnet)
    {
    }
}

💥脚本依赖注入

Magnet实现了简单的依赖注入功能,支持依赖的Type和Name匹配。

1.全局依赖注入

// 注册依赖注入
options.RegisterProvider(timerProvider);
options.RegisterProvider<ObjectKilledContext>(new ObjectKilledContext());
options.RegisterProvider(GLOBAL);
options.RegisterProvider<IObjectContext>(new HumContext(), "SELF");

2.State级别依赖注入,继承了全局依赖

var stateOptions = StateOptions.Default;
stateOptions.Identity = 666;
stateOptions.RegisterProvider(new TimerService());
var stateTest = scriptManager.CreateState(stateOptions);

脚本

public abstract class GameScript : AbstractScript
{
    [Autowired(typeof(GlobalVariableStore))]
    protected readonly GlobalVariableStore GLOBAL;

    [Autowired("SELF")]
    protected readonly IObjectContext Player;

    [Autowired]
    private readonly ITimerManager timerManager;
}

💥脚本的生命周期

脚本需要继承 AbstractScript

// 脚本初始化,在所有脚本实例创建完成之后且依赖注入完毕之后执行。
protected override void Initialize();

// 脚本停止工作,脚本被Dispose()时或MagnetScript实例调用 Unload(true) 时触发
// 触发该方法后脚本将不可用
protected override void Shutdown();

💥脚本的输出流

AbstractScript 实现了Output函数以实现输出消息至宿主。

// App.Core 内的例子 实现输出调试信息至输出流
[Conditional("DEBUG")]
public void Debug(Object @object, [CallerFilePath] String callFilePath = null, [CallerLineNumber] Int32 callLineNumber = 0, [CallerMemberName] string callMethod = null)
{
    this.Output(MessageType.Debug, $"{callFilePath}({callLineNumber}) [{callMethod}] => {@object}");
}

💥脚本之间相互调用

由于脚本state是隔离的,脚本之间无法通过变量来进行访问所以提供了调用方法

// 调用ScriptB的Test方法,出现错误会抛出异常
Call("ScriptB", "Test", []);

// 尝试调用ScriptB的PrintMessage方法,出现任何错误均不会抛出异常
TryCall("ScriptB", "PrintMessage", ["Help"]);

// 调用ScriptB的PrintMessage方法(支持强类型签名)出现错误会抛出异常
Script<ScriptB>().PrintMessage("AAA");

// 当脚本ScriptB存在时调用ScriptB的PrintMessage方法(支持强类型签名)出现错误会抛出异常
Script<ScriptB>((script) =>
{
    script.PrintMessage("BBB");
});

💥脚本调试断点

由于脚本state是隔离的,脚本之间无法通过变量来进行访问所以提供了调用方法

// 调用debug模式编译运行脚本时,执行到此处将自动打开调试器并断点暂停。
// release时此代码将被优化掉
debugget();

💥全局变量定义

由于C#的特性通过 static 定义的方法、属性、字段 均可被所有State内使用<br> 所以为了不混淆全局变量与静态变量,增加了GlobalAttribute 属性标签<br> 当字段或属性声明为static时,如果未标记[Global]属性,则编译时会产生编译警告但不影响正常运行。

[Global]
[Autowired(typeof(GlobalVariableStore))]
protected readonly static GlobalVariableStore Global;

💥宿主调用脚本内方法

为保障脚本的可卸载性,脚本的方法委托或实例均以WeakReference返回。

// 创建 stateTest中脚本ScriptA的Main方法委托
var weakMain = stateTest.MethodDelegate<Action>("ScriptA", "Main");
if (weakMain != null && weakMain.TryGetTarget(out var main))
{
    // 调用脚本Main方法
    main();
    main = null;
}

// 尝试获取stateTest内第一个实现了IPlayLifeEvent接口的脚本对象
var weakPlayerLife = stateTest.ScriptAs<IPlayLifeEvent>();
if (weakPlayerLife != null && weakPlayerLife.TryGetTarget(out var lifeEvent))
{   
    // 调用脚本的OnOnline方法
    lifeEvent.OnOnline(null);
    lifeEvent = null;
}

// 创建脚本ScriptExample中属性Target的Getter委托
var weakGetter = state?.PropertyGetterDelegate<Double>("ScriptExample", "Target");
if (weakGetter != null && weakGetter.TryGetTarget(out var getter))
{
    // 获取脚本ScriptExample中属性Target值
    Console.WriteLine(getter());
    getter = null;
}


// 创建脚本ScriptExample中属性Target的Setter委托
var weakSetter = state?.PropertySetterDelegate<Double>("ScriptExample", "Target");
if (weakSetter != null && weakSetter.TryGetTarget(out var setter))
{
    // 对脚本ScriptExample中属性Target赋值
    setter(123.45);
    setter = null;
}

💥脚本卸载

脚本卸载是不可控的,因为dotnet中的程序集卸载是由GC来决定的。<br> 宿主程序中保留脚本内类型的强引用时将会导致卸载失败。

// 卸载脚本,不会销毁所有state,由用户自己选择时机Dispose()
scriptManager.Unload();

// 强制卸载脚本,会销毁所有state
scriptManager.Unload(true);

// 申请内存 触发GC 卸载脚本
while (scriptManager.Status == ScrriptStatus.Unloading && scriptManager.IsAlive)
{
    //GC
    var obj = new byte[1024 * 1024];
    Thread.Sleep(10);
}

💥Examples

完整例子请查看 Magnet.Examples 或 Magnet.Test

    private static ScriptOptions Options(String name)
    {
        ScriptOptions options = ScriptOptions.Default;
        // 脚本名称
        options.WithName(name);
        // 调试模式 不启用脚本内置debugger()函数
        options.WithDebug(false);
        // 发布模式 编译优化
        //options.WithRelease();


        // #1 仅编译,可输出
        options.WithCompileKind(CompileKind.Compile);
        options.WithOutPutFile("123.dll");

        // #2 从程序集文件加载
        options.WithCompileKind(CompileKind.LoadAssembly);
        options.WithScanDirectory("./");
        options.WithAssemblyFileName("123.dll");

        // #3 从脚本文件编译并加载
        options.WithCompileKind(CompileKind.CompileAndLoadAssembly);
        options.WithScanDirectory("../../../../Scripts");

        // 定义自定义的编译宏符号
        options.WithCompileSymbols("USE_FILE");

        // 是否支持异步
        options.WithAllowAsync(false);

        // 添加程序集引用
        options.AddReferences<GameScript>();

        var timerProvider = new TimerProvider();
        // 增加一个分析器
        options.AddAnalyzer(timerProvider);

        // 是否支持不安全代码
        options.WithAllowUnsafe(true);

        // 替换类型
        // options.AddReplaceType(typeof(Task), typeof(MyTask));

        //禁用类型
        options.DisableType(typeof(Task));

        // 禁用泛类型的严格类型
        options.DisableType("System.Collections.Generic.List<string>");
        options.DisableType(typeof(List<String>));

        // 禁用范类型的基础类型
        options.DisableType("System.Collections.Generic.List");
        options.DisableGenericBaseType(typeof(List<>));

        // 禁用命名空间
        options.DisableNamespace(typeof(Thread));

        //禁用不安全类型与命名空间
        //options.DisableInsecureTypes();

        // 脚本类型重写器
        options.WithTypeRewriter(new TypeRewriter());
        // 使用默认的抑制诊断
        options.UseDefaultSuppressDiagnostics();
        // 脚本上下文依赖程序集加载Hook
        options.WithAssemblyLoadCallback(AssemblyLoad);
        // 注册依赖注入
        options.RegisterProvider(timerProvider);
        options.RegisterProvider<ObjectKilledContext>(new ObjectKilledContext());
        options.RegisterProvider(GLOBAL);
        options.RegisterProvider<IObjectContext>(new HumContext(), "SELF");

        return options;
    }




    private static WeakReference<Action> TestScriptUnload()
    {
        MagnetScript scriptManager = new MagnetScript(Options("Unload.Test"));
        var result = scriptManager.Compile();
        if (!result.Success)
        {
            foreach (var item in result.Diagnostics)
            {
                Console.WriteLine(item.ToString());
            }
            return null;
        }
        var state = scriptManager.CreateState();
        var weak = state.MethodDelegate<Action>("ScriptExample", "Hello");
        state.Dispose();
        scriptManager.Unload();
        return weak;
    }

    public static void Main()
    {
        MagnetScript scriptManager = new MagnetScript(Options("My.Script"));
        scriptManager.Unloading += ScriptManager_Unloading;
        scriptManager.Unloaded += ScriptManager_Unloaded;

        var result = scriptManager.Compile();
        foreach (var diagnostic in result.Diagnostics)
        {
            Console.WriteLine(diagnostic.ToString());
        }
        if (result.Success)
        {
            var stateOptions = StateOptions.Default;
            stateOptions.RegisterProvider(new TimerService());
            var stateTest = scriptManager.CreateState(stateOptions);
            var weakMain = stateTest.MethodDelegate<Action>("ScriptA", "Main");
            if (weakMain != null && weakMain.TryGetTarget(out var main))
            {
                using (new WatchTimer("With Call Main()")) main();
                main = null;
            }

            var weakPlayerLife = stateTest.ScriptAs<IPlayLifeEvent>();
            if (weakPlayerLife != null && weakPlayerLife.TryGetTarget(out var lifeEvent))
            {
                using (new WatchTimer("With Call OnOnline()")) lifeEvent.OnOnline(null);
                lifeEvent = null;
            }
            stateTest = null;
            scriptManager.Unload(true);
        }
        // wait gc unloaded assembly
        while (scriptManager.Status == ScriptStatus.Unloading && scriptManager.IsAlive)
        {
            var obj = new byte[1024 * 1024];
            Thread.Sleep(10);
        }
    }

    private static void ScriptManager_Unloaded(MagnetScript obj)
    {
        Console.WriteLine($"脚本[{obj.Name}:{obj.UniqueId}]卸载完毕.");
    }

    private static void ScriptManager_Unloading(MagnetScript obj)
    {
        Console.WriteLine($"脚本[{obj.Name}:{obj.UniqueId}]卸载请求.");
    }

Script Examples|脚本例子

    using Magnet.Core;
    using System;



    // A usable script must meet three requirements.
    // 1. The access must be public
    // 2. The [ScriptAttribute] must be marked
    // 3. The AbstractScript class must be inherited

    [Script(nameof(ScriptExample))]
    public class ScriptExample : AbstractScript
    {
        [Autowired("SELF")]
        protected readonly IObjectContext? SELF;

        [Autowired]
        protected readonly GlobalVariableStore? GLOBAL;

        [Function("Hello")]
        public void Hello()
        {
            this.PRINT($"Hello Wrold!");

            // call script method
            Call("ScriptB", "Test", []);
            Call("ScriptB", "PrintMessage", ["Help"]);
            TryCall("ScriptB", "PrintMessage1", ["Help"]);
            Script<ScriptB>().PrintMessage("AAA");
            Script<ScriptB>((script) =>
            {
                script.PrintMessage("BBB");
            });

        }



        public Double Target
        {
            get
            {
                return 3.14;
            }
            set
            {
                this.PRINT(value);
            }
        }
    }
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Magnet.Core:

Package Downloads
Magnet

基于Roslyn开发的可定制C#脚本引擎 Customizable C# scripting engine based on Roslyn.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.6 82 10/30/2024
1.0.5 86 10/26/2024
1.0.4 85 10/25/2024
1.0.3 81 10/25/2024
1.0.2 80 10/24/2024
1.0.0 93 10/24/2024