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(程序集)并没有联系在一起?
还是要回头检查代码或者知识储备。
原文:http://www.cnblogs.com/Daniel-Liang/p/5774440.html