Scut:脚本引擎
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 > )
{
LoadScriptAssemblyInfo(path); //创建该程序集的属性文件,用于生成配置清单
}
foreach (var fileName in files)
{
LoadScript(path, fileName); //将文件内容动态加载到 CSharpRuntimeScope 结构中
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(); //在CSharpRuntimeScope结构中找到代码进行编译,并将编译后的程序集加载到 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, ) == ) //将_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, );
}
}
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() > ).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 > ;
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 > )
{
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 属性控制,来决定数据库表头每个字段的属性,将表结构加入全局静态 EntitySchemaSet 进行管理
}
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(程序集)并没有联系在一起?
还是要回头检查代码或者知识储备。
8.18 补充:
在新建的 ScriptRuntimeDomain 中:
AppDomain 包含了 新的运行域;
runtimeDomain = new ScriptRuntimeDomain(typeof(ScriptRuntimeDomain).Name, new[] { _settupInfo.RuntimePrivateBinPath, ScriptCompiler.ScriptPath });
public ScriptRuntimeDomain(string name, string[] privateBinPaths)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = name;
setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
setup.PrivateBinPath = string.Join(";", privateBinPaths);
setup.CachePath = setup.ApplicationBase;
setup.ShadowCopyFiles = "true";
setup.ShadowCopyDirectories = setup.ApplicationBase;
InitDomain(name, setup);
} private void InitDomain(string name, AppDomainSetup setup)
{
#if STATIC #else
_currDomain = AppDomain.CreateDomain(name, null, setup); //创建应用程序域
var type = typeof(ScriptDomainContext);
_context = (ScriptDomainContext)_currDomain.CreateInstanceFromAndUnwrap(type.Assembly.GetName().CodeBase, type.FullName); //在新应用程序域里创建上下文的实例
#endif
}
ScriptDomainContext 包含了 动态编译的脚本程序集 执行所依赖的程序集;
foreach (var assemblyName in _settupInfo.ReferencedAssemblyNames)
{
//排除System的dll
if (string.IsNullOrEmpty(assemblyName) ||
!Path.IsPathRooted(assemblyName)) continue;
string key = Path.GetFileNameWithoutExtension(assemblyName);
runtimeDomain.LoadAssembly(key, assemblyName); //在新的应用程序域里加载“脚本程序集”运行所需的依赖程序集
} internal void LoadAssembly(string key, string assemblyName)
{
Assembly.LoadFrom(assemblyName);
_context.LoadAssembly(key, assemblyName);
}
ScriptRuntimeScope 包含了 动态编译的脚本程序集;
var scope = runtimeDomain.CreateScope(_settupInfo); private ScriptRuntimeScope CreateRuntimeScope(ScriptSettupInfo settupInfo, string amsKey, Type type)
{
#if STATIC
return type.CreateInstance<ScriptRuntimeScope>(settupInfo);
#else
return _context.GetInstance(amsKey, type.FullName, settupInfo) as ScriptRuntimeScope; //使用在新应用程序域里的“context实例”创建“脚本程序集容器Scope”
#endif
}
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);
if (files.Length > )
{
LoadScriptAssemblyInfo(path);
}
foreach (var fileName in files)
{
LoadScript(path, fileName); 加载脚本文件
}
}
}
Compile(); //新应用程序域:编译脚本文件生成程序集 存在新应用程序域中 ScriptRunTimeScope 的内存中
BuildPythonReferenceFile();
}
脚本入口句柄:
runtimeDomain.MainInstance = runtimeDomain.Scope.Execute(_settupInfo.ScriptMainProgram, _settupInfo.ScriptMainTypeName) as IMainScript;
最后调用到:
private bool CreateInstance(string scriptCode, string typeName, object[] args, out object result)
{
result = null;
Type type;
if (TryParseType(scriptCode, typeName, out type))
{
result = type.CreateInstance(args); //从scope入口进入的是新的应用程序域,反射出获取“MainClass”脚本入口
return true;
}
return false;
}
产生了新的关于应用程序域的疑问?
函数应该是执行了线程中,线程中一部分执行的是A应用程序域的程序集,一部分执行的是B应用程序域的程序集,但是从线程概念来说,一个函数用的是同一份内存。
但不是说应用程序域之间使用的内存是独立的吗?-- 一个A应用程序域的对象,里面包含了一个B应用程序域的对象是可以的吗?
继续来看一下现象:
Console.WriteLine("Current domain name = {0}.", AppDomain.CurrentDomain.FriendlyName);
runtimeDomain = new ScriptRuntimeDomain(typeof(ScriptRuntimeDomain).Name, new[] { _settupInfo.RuntimePrivateBinPath, ScriptCompiler.ScriptPath });
public override object Execute(string scriptCode, string typeName, params object[] args)
{
string code = FormatScriptCode(SettupInfo.CSharpScriptPath, scriptCode, ".cs");
object result;
if (CreateInstance(code, typeName, args, out result)) return result;
return base.Execute(scriptCode, typeName, args);
}
可以看到 Scope 里运行的代码的应用程序域名 是新域名,还是验证了我的猜想。
总体可以理解为,可执行文件运行的是主应用程序域,在主程序域创建其他程序域,并加载程序集,需要使用“透明代理”才能在主程序域运行。
而应用程序域应该有自己的 “堆栈上下文”,应用程序域之间的“代理”,可用“值传递”或“引用传递”的方式传递参数,这就能解决 “一个A应用程序域的对象,里面包含了一个B应用程序域的对象” 这个问题。
正如改图所示(两种上下文的概念有空再补齐):
Scut:脚本引擎的更多相关文章
- Scut游戏引擎改造兼容Codis。
原生的Scut引擎是采用redis来做数据缓存层,引擎在以异步的方式(时间可配置,默认100ms)实现数据同步.为了提高redis的可扩展性.高可用性,把redis换成codis,因为codis有部分 ...
- Java 8 的 Nashorn 脚本引擎教程
本文为了解所有关于 Nashorn JavaScript 引擎易于理解的代码例子. Nashorn JavaScript 引擎是Java SE 8的一部分,它与其它像Google V8 (它是Goog ...
- 【开源】.Net 动态脚本引擎NScript
开源地址: https://git.oschina.net/chejiangyi/NScript 开源QQ群: .net 开源基础服务 238543768 .Net 动态脚本引擎 NScript ...
- [No00007A]没有文件扩展".js"的脚本引擎 解决办法
在命令行运行JScript脚本时,遇到如下的错误提示: “输入错误: 没有文件扩展“.js”的脚本引擎.” 这样的错误,原因是因为JS扩展名的文件被其他软件关联了,需要取消关联. 如系统中安装了ULT ...
- 使用Lua脚本语言开发出高扩展性的系统,AgileEAS.NET SOA中间件Lua脚本引擎介绍
一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...
- cocos2dx的build_win32.dat出现问题以及install-template-msvc.dat出现.js没有脚本引擎
关于cocos2dx-2.x.x版本当中出现build_win32.bat执行失败 (针对VS2013)应当在VS的安装路径查找msbuild的文件夹,再其中查找msbuild.exe文件找到四个东西 ...
- c# 动态执行脚本,相关的几个脚本引擎.
Jint 嵌入式的javascript脚本支持引擎,一直都在更新,对各种方法支持也比较好,可以 C# 交互. https://github.com/sebastienros/jint Jurass ...
- Nmap源码分析(脚本引擎)
Nmap提供了强大的脚本引擎(NSE),以支持通过Lua编程来扩展Nmap的功能.目前脚本库已经包含300多个常用的Lua脚本,辅助完成Nmap的主机发现.端口扫描.服务侦测.操作系统侦测四个基本功能 ...
- 利用Roslyn构建一个简单的C#交互脚本引擎
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 微软的下一代编译器技术Roslyn是一个里程碑的技术,可以给.NET平台带来无限想象空间.比 ...
- C#脚本引擎 CS-Script 之(三)——如何部署
本文不但介绍了CS-Script如何部署,还介绍了CS-Script的部署后面的原理,并用一个框图详细介绍了部署中的各种细节. 一.获取资源 1.从官网上下载编译好的csscript资源:cs-scr ...
随机推荐
- Selenium webdriver 操作IE浏览器
V1.0版本:直接新建WebDriver使用 import org.openqa.selenium.WebDriver; import org.openqa.selenium.ie.InternetE ...
- Wireless Password - HDU 2825(ac自动机+状态压缩)
题目大意:有个人想破解他邻居的密码,他邻居告诉了一些关于这个密码的信息,并且给他一个单词集合,他用这些信息判断一下最少有多少种密码. 1->, 所有的密码都是有小写字母组成. 2->,密码 ...
- 拥有最小高度能自适应高度,IE、FF全兼容的div设置
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" " http://www.w3.org/TR/xh ...
- java项目使用Echarts 做柱状堆叠图,包含点击事件
基础知识请自行百度查看,以下直接贴出实现代码: <%@ page pageEncoding="UTF-8"%><!DOCTYPE html><html ...
- Git 的优点
1. 快速 如果你每移动一下鼠标都要等待五秒,是不是很受不了?版本控制也是一样的,每一个命令多那么几秒钟,一天下来也会浪费你不少时间.Git的操作非常快速,你可以把时间用在别的更有意义的地方. 2. ...
- PKU 1511 Invitation Cards (SPFA+邻接表)
题目链接:点击打开链接 题目需要求从原点到所有点的最短距离之和和所有点到原点的最短距离之和,在求所有点到原点最短距离的时候用到了一个技巧:即把图反向,求原点到所有其他点的最短距离,这样用一次SPFA就 ...
- 使用easy_install安装numpy、pandas、matplotlib及各种第三方模块
倒腾了一晚上最终把题目中的环境配好了.以下简要说明.留作资料.并共享. 1.安装python. 在cmd中能进入python环境,通过把python路径加入到系统路径中就可以实现. 2.安装easy- ...
- Qt 学习之路:存储容器
存储容器(containers)有时候也被称为集合(collections),是能够在内存中存储其它特定类型的对象,通常是一些常用的数据结构,一般是通用模板类的形式.C++ 提供了一套完整的解决方案, ...
- 微信,QQ这类IM app怎么做——谈谈Websocket
前言 关于我和WebSocket的缘:我从大二在计算机网络课上听老师讲过之后,第一次使用就到了毕业之后的第一份工作.直到最近换了工作,到了一家是含有IM社交聊天功能的app的时候,我觉得我现在可以谈谈 ...
- HDU 4607 Park Visit(树的直径)
题目大意:给定一棵树,让求出依次访问k个点的最小花费,每条边的权值都为1. 思路:如果能一直往下走不回来,那么这个路径肯定是最小的,这就取决于给定的k,但是怎么确定这个能一直走的长度呢,其实这个就是树 ...