载入中...
搜索中...
未找到
Rx.cpp
浏览该文件的文档.
1#include "rx/Rx.h"
2
3#include "event/Event.h"
4
5#include <simplesquirrel/simplesquirrel.hpp>
6
7#include <squirrel.h>
8
9#include <unordered_map>
10
11namespace eve::rx {
12
13// ---------------------------------------------------------------------------
14// Value conversion helpers.
15// ---------------------------------------------------------------------------
16int64_t Value::toInt() const {
17 switch (type) {
18 case Type::Int: return i;
19 case Type::Float: return static_cast<int64_t>(f);
20 case Type::Bool: return b ? 1 : 0;
21 case Type::String:
22 try {
23 return static_cast<int64_t>(std::stoll(s));
24 } catch (...) {
25 return 0;
26 }
27 default: return 0;
28 }
29}
30
31double Value::toFloat() const {
32 switch (type) {
33 case Type::Int: return static_cast<double>(i);
34 case Type::Float: return f;
35 case Type::Bool: return b ? 1.0 : 0.0;
36 case Type::String:
37 try {
38 return std::stod(s);
39 } catch (...) {
40 return 0.0;
41 }
42 default: return 0.0;
43 }
44}
45
46bool Value::toBool() const {
47 switch (type) {
48 case Type::Int: return i != 0;
49 case Type::Float: return f != 0.0;
50 case Type::Bool: return b;
51 case Type::String: return !s.empty();
52 case Type::Ptr: return p != nullptr;
53 default: return false;
54 }
55}
56
57std::string Value::toString() const {
58 switch (type) {
59 case Type::Int: return std::to_string(i);
60 case Type::Float: return std::to_string(f);
61 case Type::Bool: return b ? "true" : "false";
62 case Type::String: return s;
63 case Type::Ptr: return "ptr";
64 default: return "";
65 }
66}
67
68bool Value::equals(const Value& o) const {
69 if (type != o.type) return false;
70 switch (type) {
71 case Type::Int: return i == o.i;
72 case Type::Float: return f == o.f;
73 case Type::Bool: return b == o.b;
74 case Type::String: return s == o.s;
75 case Type::Ptr: return p == o.p;
76 default: return true;
77 }
78}
79
80} // namespace eve::rx
81
82// ---------------------------------------------------------------------------
83// ssq::detail specializations so Value can cross the Squirrel boundary in
84// bound method parameters / return values. Declared here so all bindings in
85// this TU instantiate against them.
86// ---------------------------------------------------------------------------
87namespace ssq {
88namespace detail {
89
90template <>
91inline void pushValue(HSQUIRRELVM vm, const eve::rx::Value& v) {
92 switch (v.type) {
93 case eve::rx::Value::Type::Nil: sq_pushnull(vm); break;
94 case eve::rx::Value::Type::Int: sq_pushinteger(vm, static_cast<SQInteger>(v.i)); break;
95 case eve::rx::Value::Type::Float: sq_pushfloat(vm, static_cast<SQFloat>(v.f)); break;
96 case eve::rx::Value::Type::Bool: sq_pushbool(vm, v.b ? SQTrue : SQFalse); break;
98 sq_pushstring(vm, v.s.c_str(), static_cast<SQInteger>(v.s.size()));
99 break;
100 case eve::rx::Value::Type::Ptr: sq_pushuserpointer(vm, v.p); break;
101 }
102}
103
104template <>
105inline eve::rx::Value popValue(HSQUIRRELVM vm, SQInteger index) {
106 switch (sq_gettype(vm, index)) {
107 case OT_NULL: return eve::rx::Value::makeNil();
108 case OT_INTEGER: {
109 SQInteger i = 0;
110 sq_getinteger(vm, index, &i);
111 return eve::rx::Value::makeInt(static_cast<int64_t>(i));
112 }
113 case OT_FLOAT: {
114 SQFloat f = 0;
115 sq_getfloat(vm, index, &f);
116 return eve::rx::Value::makeFloat(static_cast<double>(f));
117 }
118 case OT_BOOL: {
119 SQBool b = SQFalse;
120 sq_getbool(vm, index, &b);
121 return eve::rx::Value::makeBool(b == SQTrue);
122 }
123 case OT_STRING: {
124 const SQChar* s = nullptr;
125 sq_getstring(vm, index, &s);
126 return eve::rx::Value::makeString(s ? s : "");
127 }
128 case OT_USERPOINTER: {
129 SQUserPointer p = nullptr;
130 sq_getuserpointer(vm, index, &p);
132 }
133 default: return eve::rx::Value::makeNil();
134 }
135}
136
137} // namespace detail
138} // namespace ssq
139
140namespace eve::rx {
141
142// ---------------------------------------------------------------------------
143// Script-facing aliases (Value-typed).
144// ---------------------------------------------------------------------------
150
151namespace {
152
153// Call a script function with the given Value args; returns its Value result.
154Value callScript(const ssq::Function& fn, const std::vector<Value>& args, bool wantResult) {
155 if (fn.isEmpty()) return Value::makeNil();
156 HSQUIRRELVM raw = fn.getHandle();
157 SQInteger top = sq_gettop(raw);
158 sq_pushobject(raw, fn.getRaw());
159 sq_pushroottable(raw);
160 for (const auto& a : args) ssq::detail::pushValue(raw, a);
161 Value result = Value::makeNil();
162 if (SQ_SUCCEEDED(sq_call(raw, static_cast<SQInteger>(args.size() + 1),
163 wantResult ? SQTrue : SQFalse, SQTrue))) {
164 if (wantResult && sq_gettop(raw) > top) result = ssq::detail::popValue<Value>(raw, -1);
165 }
166 sq_settop(raw, top);
167 return result;
168}
169
170std::function<void()> scriptCompleted(ssq::Function fn) {
171 return [fn]() { callScript(fn, {}, false); };
172}
173std::function<void(const Value&)> scriptNext(ssq::Function fn) {
174 return [fn](const Value& v) { callScript(fn, {v}, false); };
175}
176std::function<void(const std::string&)> scriptError(ssq::Function fn) {
177 return [fn](const std::string& e) { callScript(fn, {Value::makeString(e)}, false); };
178}
179
180bool isClosure(const ssq::Object& obj) {
181 if (obj.isEmpty()) return false;
182 auto t = obj.getType();
183 return t == ssq::Type::CLOSURE || t == ssq::Type::NATIVECLOSURE;
184}
185
186// Return a copy of obj if it is a usable closure, else an empty Object.
187ssq::Object asClosure(const ssq::Object& obj) {
188 if (isClosure(obj)) return obj;
189 return ssq::Object();
190}
191
192Observer<Value> makeObserver(ssq::Object next, ssq::Object error, ssq::Object done) {
194 ssq::Object n = asClosure(next);
195 ssq::Object e = asClosure(error);
196 ssq::Object c = asClosure(done);
197 if (!n.isEmpty()) o.onNext = scriptNext(n.toFunction());
198 if (!e.isEmpty()) o.onError = scriptError(e.toFunction());
199 if (!c.isEmpty()) o.onCompleted = scriptCompleted(c.toFunction());
200 return o;
201}
202
203Subscription* doSubscribe(ObservableV* obs, ssq::Object next, ssq::Object error, ssq::Object done) {
204 if (!obs) throw eve::Exception("Rx.subscribe: null observable");
205 return new Subscription(obs->subscribe(makeObserver(next, error, done)));
206}
207
208// map(fn) : ObservableV -> ObservableV via script function returning Value.
209ObservableV* doMap(ObservableV* self, ssq::Function fn) {
210 if (!self) throw eve::Exception("Rx.map: null observable");
211 return new AnonymousObservable<Value>([self, fn](Observer<Value> out) {
213 in.onNext = [out, fn](const Value& v) { out.next(callScript(fn, {v}, true)); };
214 in.onError = [out](const std::string& e) { out.error(e); };
215 in.onCompleted = [out]() { out.completed(); };
216 return self->subscribe(std::move(in));
217 });
218}
219
220ObservableV* doFilter(ObservableV* self, ssq::Function pred) {
221 if (!self) throw eve::Exception("Rx.filter: null observable");
222 return new AnonymousObservable<Value>([self, pred](Observer<Value> out) {
224 in.onNext = [out, pred](const Value& v) {
225 if (callScript(pred, {v}, true).toBool()) out.next(v);
226 };
227 in.onError = [out](const std::string& e) { out.error(e); };
228 in.onCompleted = [out]() { out.completed(); };
229 return self->subscribe(std::move(in));
230 });
231}
232
233ObservableV* doTake(ObservableV* self, int n) {
234 if (!self) throw eve::Exception("Rx.take: null observable");
235 return new AnonymousObservable<Value>([self, n](Observer<Value> out) {
236 auto remaining = std::make_shared<int>(n);
238 in.onNext = [out, remaining](const Value& v) {
239 if (*remaining <= 0) return;
240 *remaining -= 1;
241 out.next(v);
242 if (*remaining == 0) out.completed();
243 };
244 in.onError = [out](const std::string& e) { out.error(e); };
245 in.onCompleted = [out]() { out.completed(); };
246 return self->subscribe(std::move(in));
247 });
248}
249
250ObservableV* doSkip(ObservableV* self, int n) {
251 if (!self) throw eve::Exception("Rx.skip: null observable");
252 return new AnonymousObservable<Value>([self, n](Observer<Value> out) {
253 auto remaining = std::make_shared<int>(n);
255 in.onNext = [out, remaining](const Value& v) {
256 if (*remaining > 0) {
257 *remaining -= 1;
258 return;
259 }
260 out.next(v);
261 };
262 in.onError = [out](const std::string& e) { out.error(e); };
263 in.onCompleted = [out]() { out.completed(); };
264 return self->subscribe(std::move(in));
265 });
266}
267
268ObservableV* doFirst(ObservableV* self) {
269 if (!self) throw eve::Exception("Rx.first: null observable");
270 return new AnonymousObservable<Value>([self](Observer<Value> out) {
271 auto done = std::make_shared<bool>(false);
273 in.onNext = [out, done](const Value& v) {
274 if (*done) return;
275 *done = true;
276 out.next(v);
277 out.completed();
278 };
279 in.onError = [out](const std::string& e) { out.error(e); };
280 in.onCompleted = [out]() { out.completed(); };
281 return self->subscribe(std::move(in));
282 });
283}
284
285ObservableV* doDistinctUntilChanged(ObservableV* self) {
286 if (!self) throw eve::Exception("Rx.distinctUntilChanged: null observable");
287 return new AnonymousObservable<Value>([self](Observer<Value> out) {
288 auto last = std::make_shared<Value>();
289 auto has = std::make_shared<bool>(false);
291 in.onNext = [out, last, has](const Value& v) {
292 if (*has && last->equals(v)) return;
293 *has = true;
294 *last = v;
295 out.next(v);
296 };
297 in.onError = [out](const std::string& e) { out.error(e); };
298 in.onCompleted = [out]() { out.completed(); };
299 return self->subscribe(std::move(in));
300 });
301}
302
303} // namespace
304
305// ---------------------------------------------------------------------------
306// Rx module.
307// ---------------------------------------------------------------------------
308class Rx : public Module {
309public:
311
312 SubjectV* newSubject() { return new SubjectV(); }
313 BehaviorSubjectV* newBehaviorSubject(const Value& initial) { return new BehaviorSubjectV(initial); }
314 ReplaySubjectV* newReplaySubject(int capacity = 0) { return new ReplaySubjectV(capacity); }
315 ReactivePropertyV* newProperty(const Value& initial) { return new ReactivePropertyV(initial); }
316
317 // Event bridge: fromEvent(name) returns an Observable that forwards matching
318 // messages pushed by pump() (fed from an eve::event::Event queue).
319 ObservableV* fromEvent(const std::string& name) {
320 auto subject = std::make_shared<SubjectV>();
321 {
322 std::lock_guard<std::mutex> lock(mu_);
323 bridges_[name] = subject;
324 }
325 // Return a fresh observable that subscribes to the shared subject. The
326 // returned AnonymousObservable is owned by the script; the shared subject
327 // stays alive in bridges_ so pump() can keep feeding it.
328 return new AnonymousObservable<Value>([subject](Observer<Value> out) {
329 return subject->subscribe(std::move(out));
330 });
331 }
332
333 void pump(event::Event* ev) {
334 if (!ev) return;
335 event::Message* msg = nullptr;
336 while ((msg = ev->poll()) != nullptr) {
337 std::string name = msg->name;
338 std::string data;
339 for (const auto& a : msg->args) {
340 if (a.type == event::Variant::Type::String) {
341 data = a.s;
342 break;
343 }
344 }
345 delete msg;
346 std::shared_ptr<SubjectV> subject;
347 {
348 std::lock_guard<std::mutex> lock(mu_);
349 auto it = bridges_.find(name);
350 if (it != bridges_.end()) subject = it->second;
351 }
352 if (subject) subject->onNext(Value::makeString(data));
353 }
354 }
355
356private:
357 std::mutex mu_;
358 std::unordered_map<std::string, std::shared_ptr<SubjectV>> bridges_;
359};
360
362
363// ---------------------------------------------------------------------------
364// Squirrel binding.
365// ---------------------------------------------------------------------------
366namespace {
367
368using ObsFn = std::function<ObservableV*(ObservableV*)>;
369
370void bindCommon(ssq::Class& cls) {
371 cls.addFunc("subscribe",
372 std::function<Subscription*(ObservableV*, ssq::Object)>(
373 [](ObservableV* self, ssq::Object n) { return doSubscribe(self, n, {}, {}); }));
374 cls.addFunc("subscribe3",
375 std::function<Subscription*(ObservableV*, ssq::Object, ssq::Object, ssq::Object)>(
376 [](ObservableV* self, ssq::Object n, ssq::Object e, ssq::Object d) {
377 return doSubscribe(self, n, e, d);
378 }));
379 cls.addFunc("map",
380 std::function<ObservableV*(ObservableV*, ssq::Function)>(
381 [](ObservableV* self, ssq::Function f) { return doMap(self, f); }));
382 cls.addFunc("filter",
383 std::function<ObservableV*(ObservableV*, ssq::Function)>(
384 [](ObservableV* self, ssq::Function p) { return doFilter(self, p); }));
385 cls.addFunc("take",
386 std::function<ObservableV*(ObservableV*, int)>(
387 [](ObservableV* self, int n) { return doTake(self, n); }));
388 cls.addFunc("skip",
389 std::function<ObservableV*(ObservableV*, int)>(
390 [](ObservableV* self, int n) { return doSkip(self, n); }));
391 cls.addFunc("first",
392 std::function<ObservableV*(ObservableV*)>([](ObservableV* self) { return doFirst(self); }));
393 cls.addFunc("distinctUntilChanged",
394 std::function<ObservableV*(ObservableV*)>(
395 [](ObservableV* self) { return doDistinctUntilChanged(self); }));
396}
397
398} // namespace
399
400void Rx::expose(ssq::Table& table) {
401 auto cls = table.addClass(name, Rx::create, false);
402 expose(cls);
403
404 auto obs = table.addClass<ObservableV>(
405 "Observable", std::function<ObservableV*()>([]() -> ObservableV* { return nullptr; }), true);
406 bindCommon(obs);
407
408 auto subject = table.addClass<SubjectV>(
409 "Subject", std::function<SubjectV*()>([]() -> SubjectV* { return nullptr; }), true);
410 subject.addFunc("onNext", &SubjectV::onNext);
411 subject.addFunc("onError", &SubjectV::onError);
412 subject.addFunc("onCompleted", &SubjectV::onCompleted);
413 subject.addFunc("hasObservers", &SubjectV::hasObservers);
414 bindCommon(subject);
415
416 auto behavior = table.addClass<BehaviorSubjectV>(
417 "BehaviorSubject", std::function<BehaviorSubjectV*()>([]() -> BehaviorSubjectV* { return nullptr; }), true);
418 behavior.addFunc("onNext", &BehaviorSubjectV::onNext);
419 behavior.addFunc("onError", &BehaviorSubjectV::onError);
420 behavior.addFunc("onCompleted", &BehaviorSubjectV::onCompleted);
421 behavior.addFunc("hasObservers", &BehaviorSubjectV::hasObservers);
422 behavior.addFunc("getValue", &BehaviorSubjectV::getValue);
423 behavior.addFunc("setValue", &BehaviorSubjectV::setValue);
424 bindCommon(behavior);
425
426 auto replay = table.addClass<ReplaySubjectV>(
427 "ReplaySubject", std::function<ReplaySubjectV*()>([]() -> ReplaySubjectV* { return nullptr; }), true);
428 replay.addFunc("onNext", &ReplaySubjectV::onNext);
429 replay.addFunc("onError", &ReplaySubjectV::onError);
430 replay.addFunc("onCompleted", &ReplaySubjectV::onCompleted);
431 replay.addFunc("hasObservers", &ReplaySubjectV::hasObservers);
432 bindCommon(replay);
433
434 auto prop = table.addClass<ReactivePropertyV>(
435 "ReactiveProperty", std::function<ReactivePropertyV*()>([]() -> ReactivePropertyV* { return nullptr; }), true);
436 prop.addFunc("get", &ReactivePropertyV::get);
437 prop.addFunc("set", &ReactivePropertyV::set);
438 prop.addFunc("subscribe",
439 std::function<Subscription*(ReactivePropertyV*, ssq::Object)>(
440 [](ReactivePropertyV* self, ssq::Object n) {
441 if (!self) throw eve::Exception("Rx.subscribe: null property");
442 return doSubscribe(self->asObservable(), n, {}, {});
443 }));
444 prop.addFunc("subscribe3",
445 std::function<Subscription*(ReactivePropertyV*, ssq::Object, ssq::Object, ssq::Object)>(
446 [](ReactivePropertyV* self, ssq::Object n, ssq::Object e, ssq::Object d) {
447 if (!self) throw eve::Exception("Rx.subscribe: null property");
448 return doSubscribe(self->asObservable(), n, e, d);
449 }));
450
451 auto sub = table.addClass<Subscription>(
452 "Subscription", std::function<Subscription*()>([]() -> Subscription* { return nullptr; }), true);
453 sub.addFunc("dispose", &Subscription::dispose);
454 sub.addFunc("isDisposed", &Subscription::isDisposed);
455}
456
457void Rx::expose(ssq::Class& cls) {
458 cls.addFunc("getName", &Rx::getName);
459 cls.addFunc("newSubject", &Rx::newSubject);
460 cls.addFunc("newBehaviorSubject", &Rx::newBehaviorSubject);
461 cls.addFunc("newReplaySubject", &Rx::newReplaySubject);
462 cls.addFunc("newProperty", &Rx::newProperty);
463 cls.addFunc("fromEvent", &Rx::fromEvent);
464 cls.addFunc("pump", &Rx::pump);
465}
466
467} // namespace eve::rx
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
glm::vec3 n
Definition Grass.cpp:64
std::string error
uint32_t a
uint32_t b
uint32_t c
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:24
float f
glm::vec4 p[6]
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
int d
int v
uint32_t s
Definition Weather.cpp:28
virtual std::string getName() const =0
Message * poll()
Pops the oldest message, or nullptr if the queue is empty (caller must delete).
Definition Event.cpp:49
A named event carrying an ordered list of Variant payloads. Pushed messages are heap-allocated; the q...
Definition Event.h:56
const std::vector< Variant > args
Definition Event.h:67
const std::string name
Definition Event.h:66
Internal: observable built directly from a subscribe function.
Definition Rx.h:223
Subject that replays the latest value to every new subscriber. setValue()/onNext() update the stored ...
Definition Rx.h:463
void onCompleted()
Stops replay and delivers a terminal completion.
Definition Rx.h:510
bool hasObservers() const
True while at least one live observer is registered.
Definition Rx.h:518
T getValue() const
Current stored value.
Definition Rx.h:485
void onNext(T v)
Alias of setValue().
Definition Rx.h:500
void onError(const std::string &e)
Stops replay and delivers a terminal error.
Definition Rx.h:502
void setValue(T v)
Stores a new value and pushes it to observers.
Definition Rx.h:490
Push-based stream source. Operators return a new (caller-owned) Observable; subscribe() returns a Sub...
Definition Rx.h:180
virtual Subscription subscribe(Observer< T > obs)=0
Subscribes with a full observer; returns a cancel handle.
Callback bundle pushed to a subscriber. Once error() or completed() fires the observer is stopped and...
Definition Rx.h:136
CompletedFn onCompleted
Completion callback (terminal).
Definition Rx.h:147
void error(const std::string &e) const
Delivers a terminal error and stops.
Definition Rx.h:159
void completed() const
Delivers a terminal completion and stops.
Definition Rx.h:165
NextFn onNext
Value callback.
Definition Rx.h:143
ErrorFn onError
Error callback (terminal).
Definition Rx.h:145
void next(const T &v) const
Delivers a value unless stopped.
Definition Rx.h:155
Observable value backed by a BehaviorSubject. get() returns the current value; set() stores and pushe...
Definition Rx.h:584
T get() const
Current value.
Definition Rx.h:590
void set(T v)
Stores a new value and notifies subscribers.
Definition Rx.h:592
Subject that buffers up to capacity values (0 = unlimited) and replays the buffer to every new subscr...
Definition Rx.h:532
void onNext(T v)
Buffers and pushes a new value to observers.
Definition Rx.h:551
void onCompleted()
Delivers a terminal completion to observers.
Definition Rx.h:566
void onError(const std::string &e)
Delivers a terminal error to observers.
Definition Rx.h:562
bool hasObservers() const
True while at least one live observer is registered.
Definition Rx.h:570
ReactivePropertyV * newProperty(const Value &initial)
Definition Rx.cpp:315
ReplaySubjectV * newReplaySubject(int capacity=0)
Definition Rx.cpp:314
BehaviorSubjectV * newBehaviorSubject(const Value &initial)
Definition Rx.cpp:313
SubjectV * newSubject()
Definition Rx.cpp:312
ObservableV * fromEvent(const std::string &name)
Definition Rx.cpp:319
void pump(event::Event *ev)
Definition Rx.cpp:333
Multicast push-based stream: both an Observable and a push source. Thread-safe: onNext/onError/onComp...
Definition Rx.h:371
bool hasObservers() const
True while at least one live observer is registered.
Definition Rx.h:408
void onCompleted()
Pushes a terminal completion and stops the subject.
Definition Rx.h:402
void onNext(const T &v)
Pushes a value to every registered observer.
Definition Rx.h:395
void onError(const std::string &e)
Pushes a terminal error and stops the subject.
Definition Rx.h:397
RAII-style dispose handle for an active stream subscription. Disposing unsubscribes from the source; ...
Definition Rx.h:90
void dispose()
Runs the dispose callback once; idempotent.
Definition Rx.h:114
bool isDisposed() const
True once dispose() has run (or the handle was moved from).
Definition Rx.h:123
Runtime variant used by the script-facing (Squirrel) streams. The C++ core is templated (Observer<T>/...
Definition Rx.h:19
Type type
Definition Rx.h:23
bool toBool() const
Definition Rx.cpp:46
double toFloat() const
Definition Rx.cpp:31
static Value makeFloat(double v)
Constructs a floating-point value.
Definition Rx.h:40
static Value makeNil()
Constructs a nil value.
Definition Rx.h:31
bool b
Definition Rx.h:26
static Value makePtr(void *v)
Constructs a pointer value (borrowed, not owned).
Definition Rx.h:61
int64_t toInt() const
Converters (throw eve::Exception on type mismatch).
Definition Rx.cpp:16
std::string s
Definition Rx.h:27
static Value makeInt(int64_t v)
Constructs an integer value.
Definition Rx.h:33
bool equals(const Value &o) const
Value equality across the supported types.
Definition Rx.cpp:68
double f
Definition Rx.h:25
void * p
Definition Rx.h:28
static Value makeString(std::string v)
Constructs a string value (takes ownership).
Definition Rx.h:54
int64_t i
Definition Rx.h:24
static Value makeBool(bool v)
Constructs a boolean value.
Definition Rx.h:47
std::string toString() const
Definition Rx.cpp:57
Definition Rx.cpp:11
ReactiveProperty< Value > ReactivePropertyV
Definition Rx.cpp:149
Observable< Value > ObservableV
Definition Rx.cpp:148
Subject< Value > SubjectV
Definition Rx.cpp:145
BehaviorSubject< Value > BehaviorSubjectV
Definition Rx.cpp:146
ReplaySubject< Value > ReplaySubjectV
Definition Rx.cpp:147
void pushValue(HSQUIRRELVM vm, const eve::rx::Value &v)
Definition Rx.cpp:91
eve::rx::Value popValue(HSQUIRRELVM vm, SQInteger index)
Definition Rx.cpp:105
EVEngine ECS 集成层:底层实现使用 sunxfancy/ECS.hpp https://github.com/sunxfancy/ECS.hpp
Definition ECS.h:12