xlua源码分析(三)C#访问lua的映射

xlua源码分析(三)C#访问lua的映射

上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过,C#使用LuaTable类持有lua层的table,以及使用Action委托持有lua层的function。而在xlua的官方文档中,推荐使用interface和delegate访问lua层数据结构:

映射到一个interface

这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。

映射到delegate

这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。

delegate要怎样声明呢? 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。

参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。

delegate的使用就更简单了,直接像个函数那样用就可以了。

那么这一节我们就对照着Examples 04_LuaObjectOrented,来看一下如何把包含任意数据的lua table和包含任意参数的lua function映射到C#,让C#可以直接访问。

首先看一下例子中用到的lua代码:

local calc_mt = {__index = {Add = function(self, a, b)return (a + b) * self.Multend,get_Item = function(self, index)return self.list[index + 1]end,set_Item = function(self, index, value)self.list[index + 1] = valueself:notify({name = index, value = value})end,add_PropertyChanged = function(self, delegate)if self.notifylist == nil thenself.notifylist = {}endtable.insert(self.notifylist, delegate)print('add',delegate)end,remove_PropertyChanged = function(self, delegate)for i=1, #self.notifylist doif CS.System.Object.Equals(self.notifylist[i], delegate) thentable.remove(self.notifylist, i)breakendendprint('remove', delegate)end,notify = function(self, evt)if self.notifylist ~= nil thenfor i=1, #self.notifylist doself.notifylist[i](self, evt)endend	end,}
}Calc = {New = function (mult, ...)print(...)return setmetatable({Mult = mult, list = {'aaaa','bbbb','cccc'}}, calc_mt)end
}

这个例子很简单,就是定义了一个Calc.New的函数,这个函数会使用传入的参数构建一个新的table,并设置calc_mt作为它的metatable。calc_mt的__index表中定义了若干供C#访问的函数,如Addget_Itemset_Itemadd_PropertyChangedremove_PropertyChanged

回到C#,C#层如果想要访问lua层的Calc.New,就需要定义一个和该函数匹配的委托。这个委托定义如下:

[CSharpCallLua]
public delegate ICalc CalcNew(int mult, params string[] args);

委托有一个int类型的参数mult和不定数量的string类型参数args,int和string类型都可以很容易地从C#类型转换到对应的lua类型。再看返回值,这里的返回类型是一个ICalc的interface,它其实映射就是lua层的table,也就是Calc.New所返回的那个table。为了让xlua识别CalcNew这个委托类型是用来映射lua函数的,也就是要使用这个委托调用lua层函数,需要给CalcNew类型打上CSharpCallLua的标签,这样xlua就会生成代码来完成这一工作。

映射lua table的ICalc定义如下:

[CSharpCallLua]
public interface ICalc
{event EventHandler<PropertyChangedEventArgs> PropertyChanged;int Add(int a, int b);int Mult { get; set; }object this[int index] { get; set; }
}

接口类中包含了一个PropertyChanged的event,一个Add方法,一个Multi属性,还实现了下标操作符。那么想必大家都能猜出来,这里就是分别对应了lua层calc_mt的__index表中定义的若干函数。同样地,我们也需要为这个interface打上[CSharpCallLua]标签,这样xlua就会生成一个具体实现该接口的类。

在理解映射思路之后,我们再看下测试代码:

void Test(LuaEnv luaenv)
{luaenv.DoString(script);CalcNew calc_new = luaenv.Global.GetInPath<CalcNew>("Calc.New");ICalc calc = calc_new(10, "hi", "john"); //constructorDebug.Log("sum(*10) =" + calc.Add(1, 2));calc.Mult = 100;Debug.Log("sum(*100)=" + calc.Add(1, 2));Debug.Log("list[0]=" + calc[0]);Debug.Log("list[1]=" + calc[1]);calc.PropertyChanged += Notify;calc[1] = "dddd";Debug.Log("list[1]=" + calc[1]);calc.PropertyChanged -= Notify;calc[1] = "eeee";Debug.Log("list[1]=" + calc[1]);
}void Notify(object sender, PropertyChangedEventArgs e)
{Debug.Log(string.Format("{0} has property changed {1}={2}", sender, e.name, e.value));
}

运行之后输出结果如下:

xlua源码分析(三)1

可以看到,我们通过映射的方式,访问到了lua的函数和table,而且很重要的一点是,测试代码中C#和lua实现了解耦,这种做法也是xlua的官方文档中所推荐的:

使用建议

  1. 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
  2. 如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。

那么现在,我们开始,跟着测试代码,一步步地研究背后的实现吧。

第一步,就是调用了GetInPath,通过变量的名称获取到lua函数,再将其转换为CalcNew委托类型:

public T GetInPath<T>(string path)
{
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifvar L = luaEnv.L;var translator = luaEnv.translator;int oldTop = LuaAPI.lua_gettop(L);LuaAPI.lua_getref(L, luaReference);if (0 != LuaAPI.xlua_pgettable_bypath(L, -1, path)){luaEnv.ThrowExceptionFromError(oldTop);}LuaTypes lua_type = LuaAPI.lua_type(L, -1);if (lua_type == LuaTypes.LUA_TNIL && typeof(T).IsValueType()){throw new InvalidCastException("can not assign nil to " + typeof(T).GetFriendlyName());}T value;try{translator.Get(L, -1, out value);}catch (Exception e){throw e;}finally{LuaAPI.lua_settop(L, oldTop);}return value;
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif
}

重点需要关注的其实就是这句translator.Get(L, -1, out value);,它负责对lua栈上的函数进行类型转换。这个委托类型并不是实现注册好的类型,那么就会走到通用的GetObject函数:

public void Get<T>(RealStatePtr L, int index, out T v)
{Func<RealStatePtr, int, T> get_func;if (tryGetGetFuncByType(typeof(T), out get_func)){v = get_func(L, index);}else{v = (T)GetObject(L, index, typeof(T));}
}

这个GetObject函数我们在前面的章节中也分析过,对于不是userdata的lua对象,它会寻找一个caster函数进行转换,如果找不到,则会通过一系列规则生成一个caster:

public ObjectCast GetCaster(Type type)
{if (type.IsByRef) type = type.GetElementType();Type underlyingType = Nullable.GetUnderlyingType(type);if (underlyingType != null){return genNullableCaster(GetCaster(underlyingType)); }ObjectCast oc;if (!castersMap.TryGetValue(type, out oc)){oc = genCaster(type);castersMap.Add(type, oc);}return oc;
}

这里的委托类型是我们自定义的,默认的castersMap中显然不包含,那么xlua就会为我们生成一个:

ObjectCast fixTypeGetter = (RealStatePtr L, int idx, object target) =>
{if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA){object obj = translator.SafeGetCSObj(L, idx);return (obj != null && type.IsAssignableFrom(obj.GetType())) ? obj : null;}return null;
}; if (typeof(Delegate).IsAssignableFrom(type))
{return (RealStatePtr L, int idx, object target) =>{object obj = fixTypeGetter(L, idx, target);if (obj != null) return obj;if (!LuaAPI.lua_isfunction(L, idx)){return null;}return translator.CreateDelegateBridge(L, type, idx);};
}

这里的关键也是在translator.CreateDelegateBridge这句,这个函数之前我们也分析过,它负责生成一个DelegateBridge对象。这个对象就是指代lua函数用的,它自身可以与多个C#的委托绑定。

bridge = new DelegateBridge(reference, luaEnv);
try {var ret = getDelegate(bridge, delegateType);bridge.AddDelegate(delegateType, ret);delegate_bridges[reference] = new WeakReference(bridge);return ret;
}
catch(Exception e)
{bridge.Dispose();throw e;
}

getDelegate这个函数,会根据传入的delegateType,调用DelegateBridgeBase.GetDelegateByType生成对应类型的Delegate对象,它是个virtual方法,我们在生成代码之后,就会产生继承自它的DelegateBridge.GetDelegateByTypeoverride方法,这段生成代码位于DelegatesGenBridge.cs这个文件里:

public partial class DelegateBridge : DelegateBridgeBase
{public override Delegate GetDelegateByType(Type type){if (type == typeof(System.Action)){return new System.Action(__Gen_Delegate_Imp0);}if (type == typeof(UnityEngine.Events.UnityAction)){return new UnityEngine.Events.UnityAction(__Gen_Delegate_Imp0);}if (type == typeof(System.Func<double, double, double>)){return new System.Func<double, double, double>(__Gen_Delegate_Imp1);}if (type == typeof(System.Action<string>)){return new System.Action<string>(__Gen_Delegate_Imp2);}if (type == typeof(System.Action<double>)){return new System.Action<double>(__Gen_Delegate_Imp3);}if (type == typeof(XLuaTest.IntParam)){return new XLuaTest.IntParam(__Gen_Delegate_Imp4);}if (type == typeof(XLuaTest.Vector3Param)){return new XLuaTest.Vector3Param(__Gen_Delegate_Imp5);}if (type == typeof(XLuaTest.CustomValueTypeParam)){return new XLuaTest.CustomValueTypeParam(__Gen_Delegate_Imp6);}if (type == typeof(XLuaTest.EnumParam)){return new XLuaTest.EnumParam(__Gen_Delegate_Imp7);}if (type == typeof(XLuaTest.DecimalParam)){return new XLuaTest.DecimalParam(__Gen_Delegate_Imp8);}if (type == typeof(XLuaTest.ArrayAccess)){return new XLuaTest.ArrayAccess(__Gen_Delegate_Imp9);}if (type == typeof(System.Action<bool>)){return new System.Action<bool>(__Gen_Delegate_Imp10);}if (type == typeof(Tutorial.CSCallLua.FDelegate)){return new Tutorial.CSCallLua.FDelegate(__Gen_Delegate_Imp11);}if (type == typeof(Tutorial.CSCallLua.GetE)){return new Tutorial.CSCallLua.GetE(__Gen_Delegate_Imp12);}if (type == typeof(XLuaTest.InvokeLua.CalcNew)){return new XLuaTest.InvokeLua.CalcNew(__Gen_Delegate_Imp13);}return null;}
}

得到Delegate之后,这里会将其进行缓存,这样下次遇到相同类型直接取出该委托即可。DelegateBridgeBase类缓存Delegate的数据结构比较有意思,它有一对firstKey和firstValue,然后一个Dictionary<Type, Delegate>的字典所组成,缓存时会优先将数据保存到firstKey和firstValue上,这样取出的时候就无需对字典进行查找,查找效率更高。

public bool TryGetDelegate(Type key, out Delegate value)
{if(key == firstKey){value = firstValue;return true;}if (bindTo != null){return bindTo.TryGetValue(key, out value);}value = null;return false;
}public void AddDelegate(Type key, Delegate value)
{if (key == firstKey){throw new ArgumentException("An element with the same key already exists in the dictionary.");}if (firstKey == null && bindTo == null) // nothing {firstKey = key;firstValue = value;}else if (firstKey != null && bindTo == null) // one key existed{bindTo = new Dictionary<Type, Delegate>();bindTo.Add(firstKey, firstValue);firstKey = null;firstValue = null;bindTo.Add(key, value);}else{bindTo.Add(key, value);}
}

就这样,这个新生成的委托经过辗转终于返回到了测试代码,也就是calc_new对象,那么我们就可以直接通过委托的方式调用它,此时就会触发生成的__Gen_Delegate_Imp13函数了,我们来看看生成的代码长什么样:

public XLuaTest.InvokeLua.ICalc __Gen_Delegate_Imp13(int p0, string[] p1)
{
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifRealStatePtr L = luaEnv.rawL;int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);ObjectTranslator translator = luaEnv.translator;LuaAPI.xlua_pushinteger(L, p0);if (p1 != null)  { for (int __gen_i = 0; __gen_i < p1.Length; ++__gen_i) LuaAPI.lua_pushstring(L, p1[__gen_i]); };PCall(L, 1 + (p1 == null ? 0 : p1.Length), 1, errFunc);XLuaTest.InvokeLua.ICalc __gen_ret = (XLuaTest.InvokeLua.ICalc)translator.GetObject(L, errFunc + 1, typeof(XLuaTest.InvokeLua.ICalc));LuaAPI.lua_settop(L, errFunc - 1);return  __gen_ret;
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif
}

代码逻辑很简单,就是准备调用环境,然后把C#的参数push到lua层,然后pcall调用,然后从lua栈中取出返回的结果,由于lua是弱类型的,无法事先知道返回值的类型,所以这里只能使用通用的GetObject函数对lua的返回值进行类型转换。

同样,ICalc类型是我们自定义的,默认的castersMap是不包含的,也需要生成一个caster:

return (RealStatePtr L, int idx, object target) =>
{object obj = fixTypeGetter(L, idx, target);if (obj != null) return obj;if (!LuaAPI.lua_istable(L, idx)){return null;}return translator.CreateInterfaceBridge(L, type, idx);
};

那么,这里的关键就是在translator.CreateInterfaceBridge上了,与委托非常类似,这里会根据interface的类型,寻找负责生成interface对象的函数:

public object CreateInterfaceBridge(RealStatePtr L, Type interfaceType, int idx)
{Func<int, LuaEnv, LuaBase> creator;if (!interfaceBridgeCreators.TryGetValue(interfaceType, out creator)){
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0var bridgeType = ce.EmitInterfaceImpl(interfaceType);creator = (int reference, LuaEnv luaenv) =>{return Activator.CreateInstance(bridgeType, new object[] { reference, luaEnv }) as LuaBase;};interfaceBridgeCreators.Add(interfaceType, creator);
#elsethrow new InvalidCastException("This type must add to CSharpCallLua: " + interfaceType);
#endif}LuaAPI.lua_pushvalue(L, idx);return creator(LuaAPI.luaL_ref(L), luaEnv);
}

往interfaceBridgeCreators注册creator的逻辑就是在生成代码中完成的,位于XLuaGenAutoRegister.cs中:

static void Init(LuaEnv luaenv, ObjectTranslator translator)
{wrapInit0(luaenv, translator);translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.IExchanger), XLuaTestIExchangerBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(Tutorial.CSCallLua.ItfD), TutorialCSCallLuaItfDBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.InvokeLua.ICalc), XLuaTestInvokeLuaICalcBridge.__Create);}

XLuaTestInvokeLuaICalcBridge是继承自ICalc接口的类,它负责实现ICalc的功能,也就是我们一开始提到的一个PropertyChanged的event +=和-=操作,一个Add方法,一个Multi属性,以及下标操作符。__Create方法就是简单了返回了一个XLuaTestInvokeLuaICalcBridge对象:

public class XLuaTestInvokeLuaICalcBridge : LuaBase, XLuaTest.InvokeLua.ICalc
{public static LuaBase __Create(int reference, LuaEnv luaenv){return new XLuaTestInvokeLuaICalcBridge(reference, luaenv);}
}

有了ICalc对象后,我们再次回到例子中,例子中接下来调用了Add方法与Multi的set属性,XLuaTestInvokeLuaICalcBridge类对它们的实现都比较简单,这里就不再赘述了。接下来是下标访问,对于get来说会去尝试访问lua层的get_item函数,而对于set来说则会去访问lua层的set_item函数。例子里还往PropertyChanged事件中注册了一个Notify方法,这时则会触发lua层的add_PropertyChanged函数,把C#的Notify方法push到lua层。

上一节我们提到,把C#对象push到lua层时,会调用到xlua的getTypeId方法,用来获取表示对象类的唯一ID,对于Notify方法来说,它就是一个委托,而委托实质上使用的是同一个type id:

if (typeof(MulticastDelegate).IsAssignableFrom(type))
{if (common_delegate_meta == -1) throw new Exception("Fatal Exception! Delegate Metatable not inited!");TryDelayWrapLoader(L, type);return common_delegate_meta;
}

TryDelayWrapLoader我们上一节分析过,这里就不展开了,由于没有wrap,还是通过反射生成类的各种table。最终lua层缓存了一个表示C# Notify方法的userdata。

此时再对table进行set_item,就会触发Notify方法调用了,对于delegate来说,xlua在初始化时就往metatable里设置了__call元方法:

public void CreateDelegateMetatable(RealStatePtr L)
{Utils.BeginObjectRegister(null, L, this, 3, 0, 0, 0, common_delegate_meta);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__call", StaticLuaCallbacks.DelegateCall);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__add", StaticLuaCallbacks.DelegateCombine);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__sub", StaticLuaCallbacks.DelegateRemove);Utils.EndObjectRegister(null, L, this, null, null,typeof(System.MulticastDelegate), null, null);
}[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int DelegateCall(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);object objDelegate = translator.FastGetCSObj(L, 1);if (objDelegate == null || !(objDelegate is Delegate)){return LuaAPI.luaL_error(L, "trying to invoke a value that is not delegate nor callable");}return translator.methodWrapsCache.GetDelegateWrap(objDelegate.GetType())(L);}catch (Exception e){return LuaAPI.luaL_error(L, "c# exception in DelegateCall:" + e);}
}

GetDelegateWrap方法就是根据委托的类型,反射取出它的Inovke方法,然后包装到MethodWrap的Call方法中,进行最终的反射调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/198961.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

<b><strong>,<i><em>标签的区别

1. b标签和strong标签 b标签&#xff1a;仅仅是UI层面的加粗样式&#xff0c;并不具备HTML语义 strong标签&#xff1a;不仅是在UI层面的加粗样式&#xff0c;具备HTML语义&#xff0c;表示强调 2. i标签和em标签 i 标签&#xff1a;仅仅是UI层面的斜体样式&#xff0c;并不…

Django学习日志08

如何开启事务 事务的目的&#xff1a;为了保证多个SQL语句执行成功&#xff0c;执行失败&#xff0c;前后保持一致&#xff0c;保证数据安全 ACID属性&#xff1a; A&#xff1a;原子性&#xff08;Atomicity&#xff09;&#xff1a;指事务是原子的&#xff0c;对事务中的操…

系统设计之通讯协议

一、通讯协议 架构风格定义了应用程序编程接口 (API) 的不同组件如何相互交互。因此&#xff0c;它们通过提供设计和构建 API 的标准方法来确保效率、可靠性以及与其他系统集成的便捷性。以下是最常用的样式&#xff1a; 1. SOAP 成熟、全面、基于XML 最适合于企业应用 可扩展…

STM32获取最大堆栈空间

参考 stackflow相关讨论 原理 通过参考链接&#xff0c;可知探测Stack的最大深度是先在stack中填充不常用的特定值&#xff0c;然后实时检测这些值哪些发生了变化&#xff0c;变化的表示使用到了这个空间&#xff0c;如果程序完全遍历后&#xff0c;有些值还是没变&#xff…

网络运维与网络安全 学习笔记2023.11.19

网络运维与网络安全 学习笔记 第二十天 今日目标 STP工作原理、STP高级配置、MSTP工作原理 MSTP配置案例、MSTP负载均衡 STP工作原理 单点故障 PC之间的互通链路仅仅存在1个 任何一个设备或链路出现问题&#xff0c;PC之间都会无法通信 解决方案 增加冗余/备份设备 增加冗…

9 HDFS架构剖析

问题 100台服务器&#xff0c;存储空间单个200GB 20T 5T文件如何存储&#xff1f; 128MB一块 128MB81GB 1288*10241TB 5T数据分成的128MB的块数 8192 * 5 客户端(client)代表用户通过与namenode和datanode交互来访问整个文件系统。 HDFS集群有两类节点&#xff1a; 一个na…

成为电车销量的“中坚力量”,微小型车不能只有“低价”?

日常交通中&#xff0c;越来越多的汽车开始“绿牌出行”&#xff0c;市场的最新销量也不断验证着新能源车抢占更多市场的事实。 11月初&#xff0c;国内多家车企公布10月销量数据&#xff0c;其中新能源汽车销量增长仍然亮眼。根据中国工业和信息化部数据&#xff0c;我国汽车…

Nessus扫描结果出现在TE.IO或者ES容器结果查看问题解决方案

Nessus扫描结果出现在TE.IO或者ES容器结果查看问题解决方案 也是昨天晚上折腾了一个晚上到凌晨四点多,实在没有头绪,在论坛,贴吧,各种求助查贴,没有什么人解决.后面请教了一个安全圈的大佬朋友给解决了. 我的问题是在kali上的,所以只写了kali 的解决方案: 修改插件: vim /opt/…

Hadoop-- hdfs

1、HDFS中的三个进程&#xff1a;NameNode&#xff08;NN&#xff09;、DataNode(DN)、SecondNameNode(SNN) 2、NameNode&#xff08;NN&#xff09; 1、作用&#xff1a; 1、接收客户端的一个读、写的服务&#xff0c;在namenode上存储了数据文件和datanode的映射的关系。 …

【Q1—45min】

1.epoll除了边沿触发还有什么&#xff1f;与select区别. epoll 是Linux平台下的一种特有的多路复用IO实现方式&#xff0c;与传统的 select 相比&#xff0c;epoll 在性能上有很大的提升。 epoll是一种当文件描述符的内核缓冲区非空的时候&#xff0c;发出可读信号进行通知&…

划片机新手教程:从准备工作到注意事项全解析!

随着科技的飞速发展&#xff0c;划片机已成为半导体行业不可或缺的一部分。对于新手来说&#xff0c;如何正确操作划片机显得尤为重要。以下是新手操作划片机的步骤和建议。 一、准备工作 在开始操作划片机之前&#xff0c;首先需要准备好以下工具和材料&#xff1a; 1. 划片机…

Java(三)(static,代码块,单例设计模式,继承)

目录 static 有无static修饰的成员变量 有无static修饰的成员方法 static的注意事项 代码块 静态代码块 实例代码块 单例设计模式 饿汉式单例写法 懒汉式单例写法 继承 基本概念 注意事项 权限修饰符 单继承 object 方法重写 子类方法中访问其他成员(成员变量…

Flume学习笔记(3)—— Flume 自定义组件

前置知识&#xff1a; Flume学习笔记&#xff08;1&#xff09;—— Flume入门-CSDN博客 Flume学习笔记&#xff08;2&#xff09;—— Flume进阶-CSDN博客 Flume 自定义组件 自定义 Interceptor 需求分析&#xff1a;使用 Flume 采集服务器本地日志&#xff0c;需要按照日志…

交易机器人-微信群通知

微信公众号:大数据高性能计算 1 背景 背景是基于人工去做交易本身无法做到24小时无时无刻的交易,主要是虚拟币本身它是24小时交易,人无法做到24小时盯盘,其次就是如果你希望通过配置更加复杂的规则甚至需要爬取最新的信息走模型进行量化交易的时候,就需要自己去做一些量化…

Linux 环境搭建

✨个人主页&#xff1a; Anmia.&#x1f389;所属专栏&#xff1a; C Language &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 本章概要 1. 认识 Linux, 了解 Linux 的相关背景 2. 学会如何使用云服务器 3. 掌握使用远程终端工具 xshell 登陆 Linux 服务器 1. Li…

语义检索系统【全】:基于milvus语义检索系统指令全流程-快速部署版

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

学生邮箱白嫖/免费安装JetBrains全家桶(IDEA/pycharm等) —— 保姆级教程

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对博主首页也很感兴趣o (ˉ▽ˉ&#xff1b;) 博主首页&#xff0c;更多redis、java等优质好文以及各种保姆级教程等您挖掘&#xff01; 目录 前言 JetBrains全家桶介绍 申请过程&#xff1a; 获取学…

C++类与对象(2)—构造函数析构函数

目录 一、类的6个默认成员函数 二、构造函数 1、定义 2、特征 三、析构函数 1、定义 2、特征 四、默认生成构造&析构 1、定义 2、内置类型 3、自定义类型 4、声明处给默认值 5、总结 下一篇 一、类的6个默认成员函数 如果一个类中没有定义任何成员&#xff0…

Idea2023 Springboot web项目正常启动,页面展示404解决办法

Idea2023 Springboot web项目正常启动,页面展示404解决办法 问题&#xff1a; 项目启动成功&#xff0c;但是访问网页&#xff0c;提示一直提示重定向次数过多&#xff0c;404 解决方法 在IDEA的Run/Debug Configurations窗口下当前的Application模块的Working directory中添…

python 对图像进行聚类分析

import cv2 import numpy as np from sklearn.cluster import KMeans import time# 中文路径读取 def cv_imread(filePath, cv2_falgcv2.COLOR_BGR2RGB): cv_img cv2.imdecode(np.fromfile(filePath, dtypenp.uint8), cv2_falg) return cv_img# 自定义装饰器计算时间 def…