尽量从需要理解的顺序去记录
传入当中有几个直观的需求:一个是函数的定义,然后可以找到对应函数参数的类型,而这些类型是已经被存储和统计的。
fn some_system(a: Res<SomeComponentData>) {}
fn some_system_2(a: Res<SomeComponentData,SomeComponentData2>) {}这样然后可以将对应的SomeComponentData那里去获取内容。
HashMap::new::<TypeId, Box<Dyn Any>>()然后里面的Box则是为了获取任何未知的内容,且用智能指针包裹。
从一开始的教程,是使用
impl<F: FnMut()> System<()> for F {
fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
(self)()
}
}以上的解释:创建一些任意泛型下为FnMut的无参数类型去实施System trait的行为。 然后从拓展参数量下,改写为
impl<F: FnMut(T1), T1: 'static> System<(T1,)> for F {}
impl<F: FnMut(T1, T2), T1: 'static, T2: 'static> System<(T1, T2)> for F {}来满足在接纳多个参数量下,在各种参数从类型ID去找对应的数据内容。
比如之前的
// type T = Input
trait System<Input> {}
/// 到
trait System{}
// 从原本的
for F {}
// 转到了
for FunctionSystem<(), F> {}也就是说,以前的System的trait是固定了fn run,而run的关于参数的类型从FunctionSystem的签名中代入,现在还是不是非常容易理解。
// 从用户的角度看到的是
fn my_system(a:Res<Running>){}
app.add_system(my_system);我在add_system去拣选my_system函数中的参数里面的类型 a:Res,那么我可以获取 Res的泛型, 我可以获取其之前Stored的东西,比如我已经设置了
#[Component]
struct Running{
speed: i32
}这样的东西。 这个链路简单的理解目前是这样。 然后回归到上面说的,进而我们去统一地表达system的函数,那么我们去定义一个trait。如果在typescript或者任何面向对象的语言中,可能容易去定义接口或者一个class去保存它的typeid和参数的类型乃至数值或者字符串。
由于rust可以随时为任意类型去添加新的trait的行为实施。 乃至对于任何一个类型我们都会实施对于system的行为执行。 所有会产生了关于 impl System for F {} 的东西需求。 也就是之前说的
impl<T> System for F
where T: FnMut<()>
{}文中提及到这会永久地从资源库中移除资源。在我们的ECS系统中如果有一个system拿出来了就还不回去了,别的system就无法再次获取也就不合理了。 原因是我们现在使用的Box的,Box的拿出来使用,是会转移所有权的。那么我们进而对拿出来的东西进一步作处理了。 所有会有关于 ErasedSystem 的trait的定义了。并且就会被转移到一个新的 hashmap 中,而且基本跟前面保持原型。
trait ErasedSystem {
fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>);
}
impl<S: System<I>, I> ErasedSystem for S {
fn run(&mut self, resources: &mut HashMap<TypeId, Box<dyn Any>>) {
<Self as System<I>>::run(self, resources);
}
}然后产生了一个关于I无法确定是什么类型的错误。 这里很懵了,先看一下原本的关于System的时候,是什么样的原来的代码。
// 从一开始的
trait System<Input>{}
// 然后到
trait System{}而这个 缩减了system的泛型,是考虑到了这I类型的定向问题,所以才改为了trait System 的,也就是我们就是因为 System 的存在,而把路走绝了,我们不能使用泛型去特质化System了。我们需要将System的数据区别出来了,System的语义仅仅在行为的执行特质上,而System的具体内容和执行方式都存都了一个新的类型中。所有这就产生一个FunctionSystem的东西。
首先原本的 F: FnMut<()> 和 F: FnMut<T1,T2> 这几个对一些接下来定义的类型的泛型约束,在实施system的拓展时,在这一步就统一一下这个多样化的入口,FnMut(T),FnMut(T,U)这些统一为一个FunctionSystem<..,F>来包裹,而这个F,就是原本需要定义的类型本身,作为这个FunctionSystem去接待定义进来的F服务,然后加以包装去使用。
更需要整理的是system从多种需要用到的工具中转换到了多种的类型, 类型和类型之间的交互,如何在代码层面实现出来。
从一个单function下,记录了对应了函数中的参数从TypeId用HashMap记录它的FnMut内容
pub fn add_system<I, S: System + 'static>(&mut self, system: impl IntoSystem<I, System = S>) {
self.systems.push(Box::new(system.into_system()));
}这个函数中,参数trait对象下的self,和关于从IntoSystem中获取对应了关于I,和System=S的system的处理。 然后对于I,System=S, I是从最外部传入,先放一放讨论,然后到System=S,里面的代码是
pub trait IntoSystem<Input> {
// 这里是定义一个关联类型System
type System: System;
// Input看作普通泛型 T。
fn into_system(self) -> Self::System;
}
impl<F: FnMut()> IntoSystem<()> for F {
type System = FunctionSystem<(), Self>;
fn into_system(self) -> Self::System {
FunctionSystem {
f: self,
marker: Default::default(),
}
}
}
impl<F: FnMut(T1), T1: 'static> IntoSystem<(T1,)> for F {
type System = FunctionSystem<(T1,), Self>;
fn into_system(self) -> Self::System {
FunctionSystem {
f: self,
marker: Default::default(),
}
}
}(我在代码块的注释里说明)
其实我们可以主要去看看T1的走向。 从各种的 impl 来看,关于F的是概括为都是FnMut(..)的特征拓展实施。 从中包括了关于 IntoSystem, 和原本就是F的,但是包裹了新的一层是 FunctionSystem<Input,F> 内接的F。 其实道理还是一样。 因为为了表达出参数 Input 的与 F 的关系,用一个结构体, 第一个存为自身的一个类型FunctionSystem,然后用PhantomData解释为Input泛型与FunctionSystem的关系。
回归到原始想要的东西
impl System for F {}这里的F意思是说我写一些关于像System的函数出来,那么我的F可以解析出关于我函数的参数出来, 所以可以这样写写
impl Sysmte for Function {} 大概是./src/analyics/mod.rs的意思。
那么从操作语义上面去看,可以说原本的一个Function类型的, 而且有Fn,FnMut, 等的trait, 所以似乎就是为了模拟子类型去包裹一下对应这个函数的一些信息,phantomdata是为了满足编译器编译的。(补充:还为了标记类型关系,确保是Send/Sync)
所以务必在有一个全面的理解phantomdata下才能继续对类型系统的操作延伸性。 其本质上说明的是,子类型与函数之间的协变/逆协变关系。