首页 > 其他 > 详细

Scut:脚本引擎

时间:2016-08-16 23:57:17      阅读:559      评论:0      收藏:0      [点我收藏+]

  Scut 可以执行 C#、Python、Lua 三种类型的脚步,Scut 是如何加载并传递参数的呢?

  首先值得注意的是:Scut 在编译时就会将逻辑层脚本源码复制到bin/Script的目录下。

1. ScriptRuntimeDomain、ScriptRuntimeScope、ScriptDomainContext

    public abstract class ScriptBaseScope : IDisposable
    {
        protected readonly ScriptSettupInfo SettupInfo;   //脚本配置信息
        protected readonly List<string> WatcherPathList;  //被监控文件列表
        private string[] _rootPathArr;                    //根路径拆解
        protected string _modelAssemblyPath;           //model程序集路径
        protected Assembly _modelAssembly;                //model程序集
        protected string _csharpAssemblyPath;          //C#程序集路径
        protected Assembly _csharpAssembly;           //C#程序集
    }

  疑问:这里为什么将程序集拆成C#Assembly与ModelAssemble?

  LuaRuntimeScope 包含了 ScriptBaseScope;

  PythonRuntimeScope 包含了 LuaRuntimeScope;

  CSharpRuntimeScope 包含了 PythonRuntimeScope;

     public override void Init()
        {
            _modelCodeCache = new DictionaryExtend<string, ScriptFileInfo>();  //model 文件夹下脚本加载所需的代码缓存
            _csharpCodeCache = new DictionaryExtend<string, ScriptFileInfo>();  //C# 文件夹下脚本加载所需的代码缓存
            _modelScriptPath = Path.Combine(SettupInfo.RuntimePath, SettupInfo.ScriptRelativePath, SettupInfo.ModelScriptPath);
            AddWatchPath(_modelScriptPath, FileFilter);  //model 文件夹监视

            _csharpScriptPath = Path.Combine(SettupInfo.RuntimePath, SettupInfo.ScriptRelativePath, SettupInfo.CSharpScriptPath);
            AddWatchPath(_csharpScriptPath, FileFilter)   //CSharp 文件夹监视
        Load(); 
        private void Load()
        {
            var pathList = new String[] { _modelScriptPath, _csharpScriptPath };

            foreach (var path in pathList)
            {
                if (Directory.Exists(path))
                {
                    var files = Directory.GetFiles(path, FileFilter, SearchOption.AllDirectories);  //过滤出 bin/Script 目录下的所有 .cs 文件
                    if (files.Length > 0)
                    {
                        LoadScriptAssemblyInfo(path);  //创建该程序集的属性文件,也可做反射之用,这里采用动态生成 assemble 文件的方式,方便动态替换单个文件做热更新
                    }
                    foreach (var fileName in files)
                    {
                        LoadScript(path, fileName);    //将文件内容动态加载到内存
        private ScriptFileInfo LoadScript(string scriptPath, string fileName)
        {
            ScriptFileInfo scriptFileInfo = null;
            string scriptCode = GetScriptCode(fileName);    //以相对地址+文件前缀作为 key;
            scriptFileInfo = CreateScriptFile(fileName);   //将文件源码作为 value;
            if (scriptFileInfo != null)
            {
                if (scriptPath == _modelScriptPath)
                {
                    _modelCodeCache[scriptCode] = scriptFileInfo;
                }
                else
                {
                    _csharpCodeCache[scriptCode] = scriptFileInfo;
                }
            }
            return scriptFileInfo;
        }
                    }
                }
            }
            Compile();  //编译,并将编译后的程序集加载到了BaseRuntimeScope中了
            BuildPythonReferenceFile();
        }

        base.Init();   //执行C#、python、lua runtimescope 的初始化API
     }

  ScriptRuntimeScope 管理了具体的程序集,ScriptRuntimeDomain 则是负责将程序集运行在具体的运行域中。

  ScriptDomainContext 的作用暂时不是很清楚。

 

2. ScriptEngine

        public static void Initialize()
        {
            try
            {
                ScriptCompiler.ClearScriptRuntimeTemp();                   //删除运行目录下的ScriptRuntimeDomain目录
                ConfigManager.ConfigReloaded += OnScriptSettingReLoad;     //配置文件重载时的钩子API
                var scope = InitScriptRuntimeScope();
        private static ScriptRuntimeScope InitScriptRuntimeScope()
        {
            //star compile
            if (Interlocked.Exchange(ref _isCompiling, 1) == 0)   //将_isCompiling设为1,并返回原值
            {
                ScriptRuntimeDomain runtimeDomain = null;
                try
                {
                    string runtimePath = MathUtils.RuntimePath ?? MathUtils.RuntimeBinPath;   //当前者为null时,取后者的值
                    AppDomain.CurrentDomain.AppendPrivatePath(ScriptCompiler.ScriptPath);     //增加专用路径:运行时路径/temp
                    runtimeDomain = new ScriptRuntimeDomain(typeof(ScriptRuntimeDomain).Name, new[] { _settupInfo.RuntimePrivateBinPath, ScriptCompiler.ScriptPath });  //为脚本创建新的运行域
                    foreach (var assemblyName in _settupInfo.ReferencedAssemblyNames)         //_setupInfor 是脚本的安装信息,通过配置读取获得,包括C#、Python、Lua脚本所在的路径、模块的路径等参数--加载脚本运行所需的依赖库
                    {
                        //排除System的dll
                        if (string.IsNullOrEmpty(assemblyName) ||     
                            !Path.IsPathRooted(assemblyName)) continue;
                        string key = Path.GetFileNameWithoutExtension(assemblyName);
                        runtimeDomain.LoadAssembly(key, assemblyName);   //从这里可以发现ScriptDomainContext主要是管理依赖库的,也就是脚本运行的上下文环境
                    }
                    var scope = runtimeDomain.CreateScope(_settupInfo);  //在运行域上创建并初始化管理程序集的结构,并动态加载程序集至 _modelAssemble _csharpAssemble
                    //ignore error, allow model is empty.
                    if (scope == null)
                    {
                        if (_runtimeDomain == null) _runtimeDomain = runtimeDomain;
                        return scope;
                    }

                    //update befor
                    bool isFirstRun = _runtimeDomain == null;  //在下面才对 _runtimeDomian 赋值,而这里做一个开关?
                    if (!isFirstRun && _settupInfo.ModelChangedBefore != null)
                    {
                        if (_runtimeDomain.Scope.ModelAssembly != null) _settupInfo.ModelChangedBefore(_runtimeDomain.Scope.ModelAssembly);
                        TimeListener.Clear();
                        if (_runtimeDomain.MainInstance != null) _runtimeDomain.MainInstance.Stop();
                    }
                    runtimeDomain.MainInstance = runtimeDomain.Scope.Execute(_settupInfo.ScriptMainProgram, _settupInfo.ScriptMainTypeName) as IMainScript;  //从 _csharpAssembly 处获取 MainClass 的句柄
                    if (_runtimeDomain != null)
                    {
                        //unload pre-domain
                        _runtimeDomain.Dispose();
                    }
                    _runtimeDomain = runtimeDomain;  //在这里才对 _runtimeDomain 赋值是何意?
                    EntitySchemaSet.EntityAssembly = scope.ModelAssembly;
                    //update after
                    if (!isFirstRun && _settupInfo.ModelChangedAfter != null && scope.ModelAssembly != null)
                    {
                        _settupInfo.ModelChangedAfter(scope.ModelAssembly);
                    }
                    else if (scope.ModelAssembly != null)
                    {
                        ProtoBufUtils.LoadProtobufType(scope.ModelAssembly);  //比较重要的代码,Model文件夹下的代码也支持protobuf的继承
                        EntitySchemaSet.LoadAssembly(scope.ModelAssembly);
                    }
                    PrintCompiledMessage();
                    //replace runtime 
                    if (!isFirstRun && runtimeDomain.MainInstance != null)
                    {
                        runtimeDomain.MainInstance.ReStart();
                    }
                    return scope;
                }
                finally
                {
                    Interlocked.Exchange(ref _isCompiling, 0);
                }
            }
            else
            {
                TraceLog.WriteLine("{1} {0} has not compiled in other thread.", "model", DateTime.Now.ToString("HH:mm:ss"));
            }
            return null;
        }
       if (scope != null)
                {
                    InitScriptListener(scope.WatcherPaths);  //对脚本文件目录进行监控
                }
            }
            catch (Exception er)
            {
                IsError = true;
                TraceLog.WriteError("Script init error:{0}.", er);
                throw er;
            }
        }

   在之前使用 protobuf 结构的类中,是不能有继承的,否则反序列化会出问题,这里有所解决?到时候试验一下。

   单独来看一下 LoadAssembly:

        public static void LoadAssembly(Assembly assembly)
        {
            TraceLog.WriteLine("{0} Start checking table schema, please wait.", DateTime.Now.ToString("HH:mm:ss"));

            EntityAssembly = assembly;
            var types = assembly.GetTypes().Where(p => p.GetCustomAttributes(typeof(EntityTableAttribute), false).Count() > 0).ToList();  //实体必须包含 EntityTable
            foreach (var type in types)  //遍历所有实体类型
            {
                InitSchema(type);        //为每个类型构建数据库映射表
            }
            TraceLog.WriteLine("{0} Check table schema successfully.", DateTime.Now.ToString("HH:mm:ss"));
        }

  再看 InitSchema:

        public static SchemaTable InitSchema(Type type, bool isReset = false)
        {
            SchemaTable schema;
            if (!isReset && TryGet(type, out schema))
            {
                return schema;
            }
            schema = new SchemaTable();
            try
            {
                schema.IsEntitySync = type.GetCustomAttributes(typeof(EntitySyncAttribute), false).Length > 0;
                schema.EntityType = type;
                //加载表
                var entityTable = FindAttribute<EntityTableAttribute>(type.GetCustomAttributes(false));  //获取该实体类型的 EntityTable 的具体属性定义
                if (entityTable != null)  //并逐一复制给该实体类型对应 的 数据库映射结构
                {
                    schema.AccessLevel = entityTable.AccessLevel;    
                    schema.StorageType |= entityTable.StorageType;
                    schema.CacheType = entityTable.CacheType;
                    schema.IncreaseStartNo = entityTable.IncreaseStartNo;
                    schema.IsExpired = entityTable.IsExpired;
                    schema.EntityName = string.IsNullOrEmpty(entityTable.TableName) ? type.Name : entityTable.TableName;
                    schema.ConnectKey = string.IsNullOrEmpty(entityTable.ConnectKey) ? "" : entityTable.ConnectKey;
                    schema.Indexs = entityTable.Indexs;
                    schema.Condition = entityTable.Condition;
                    schema.OrderColumn = entityTable.OrderColumn;
                    //schema.DelayLoad = entityTable.DelayLoad;
                    schema.NameFormat = entityTable.TableNameFormat;
                    SetPeriodTime(schema);
                    //model other set
                    if (entityTable.IsExpired && entityTable.PeriodTime > 0)
                    {
                        schema.PeriodTime = entityTable.PeriodTime;
                    }
                    schema.Capacity = entityTable.Capacity;
                    schema.PersonalName = entityTable.PersonalName;

                    if (string.IsNullOrEmpty(schema.ConnectKey)
                        && type == typeof(EntityHistory))
                    {
                        schema.IsInternal = true;
                        var dbPair = DbConnectionProvider.Find(DbLevel.Game);
                        if (dbPair.Value == null)
                        {
                            dbPair = DbConnectionProvider.FindFirst();
                        }
                        if (dbPair.Value != null)
                        {
                            schema.ConnectKey = dbPair.Key;
                            schema.ConnectionProviderType = dbPair.Value.ProviderTypeName;
                            schema.ConnectionString = dbPair.Value.ConnectionString;
                        }
                        //else
                        //{
                        //    TraceLog.WriteWarn("Not found Redis‘s history record of db connect config.");
                        //}
                    }
                }
                InitSchema(type, schema);  //进一步分析该实体类型的成员属性, 由 EntityField 属性控制,来决定数据库表头每个字段的属性
            }
            catch (Exception ex)
            {
                TraceLog.WriteError("InitSchema type:{0} error:\r\n{1}", type.FullName, ex);
            }
            //check cachetype
            if ((schema.CacheType == CacheType.Entity && !type.IsSubclassOf(typeof(ShareEntity))) ||
                (schema.CacheType == CacheType.Dictionary &&
                    schema.AccessLevel == AccessLevel.ReadWrite &&
                    !type.IsSubclassOf(typeof(BaseEntity)))
                )
            {
                throw new ArgumentException(string.Format("\"EntityTable.CacheType:{1}\" attribute of {0} class is error", type.FullName, schema.CacheType), "CacheType");
            }
            return schema;
        }

 

疑问:

  通篇代码阅读下来,一个疑问是“获取 MainClass 句柄后,通过 MainClass 调用其他脚本是一个怎么样的过程?”,因为好像并没有将它们同时加载在一个运行域上?

  看首次启动的代码:

        protected virtual async System.Threading.Tasks.Task RunAsync()
        {
        ...if (ScriptEngines.RunMainProgram())
        ...  await RunWait();
        }

  进一步加重了疑惑,这个疑问简而言之就是 ScriptRuntimeDomain 里的 AppDomain(运行域) 与 ScriptRuntimeScoe(程序集)并没有联系在一起?

  还是要回头检查代码或者知识储备。

  

Scut:脚本引擎

原文:http://www.cnblogs.com/Daniel-Liang/p/5774440.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!