virid-core 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ # --- 依赖与包管理器 ---
2
+ .venv/
3
+ uv.lock
4
+ __pycache__/
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: virid-core
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.10
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "virid-core"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.10"
5
+ dependencies = []
6
+
7
+ [tool.hatch.build.targets.wheel]
8
+ packages = ["src/virid"] # 告诉 Hatch 顶层是个命名空间
9
+ layout = "src" # 核心:使用标准的 src 布局逻辑
10
+
11
+ [build-system]
12
+ requires = ["hatchling"]
13
+ build-backend = "hatchling.build"
@@ -0,0 +1,18 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from .app import ViridApp,ViridPlugin
7
+ from .core import *
8
+ from .decorators import *
9
+ from .util import toggle_utils
10
+
11
+
12
+ def create_virid(
13
+ enable_logging: bool = True,
14
+ ) -> ViridApp:
15
+ from .app import virid_app
16
+
17
+ toggle_utils(enable_logging)
18
+ return virid_app
@@ -0,0 +1,145 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from .core import Engine
9
+ from .container import Container
10
+ from typing import Type, Callable, TypeVar, overload, Any, Protocol
11
+ from .core.message import BaseMessage, EventMessage, SingleMessage
12
+ from .core.interface import TickHook, ExecuteHook, Middleware
13
+ from .core.io import MessageWriter
14
+
15
+ T = TypeVar("T")
16
+ F = TypeVar("F", bound=EventMessage, contravariant=True)
17
+ H = TypeVar("H", bound=SingleMessage, contravariant=True)
18
+ O = TypeVar("O", contravariant=True)
19
+
20
+ installed_plugins = set()
21
+
22
+
23
+ class ViridPlugin(Protocol[O]):
24
+ name: str
25
+
26
+ def install(self, app: ViridApp, options: O) -> None: ...
27
+
28
+
29
+ class ViridApp:
30
+ def __init__(self):
31
+ self.engine = Engine()
32
+ self.container = Container()
33
+ self.activate: list[Callable] = [lambda x: x]
34
+
35
+ def on_activate(self, activate: Callable, front: bool = False) -> None:
36
+ if front:
37
+ self.activate.insert(0, activate)
38
+ else:
39
+ self.activate.append(activate)
40
+
41
+ def get(self, identifier: Type[T]) -> T:
42
+ return self.container.get(identifier, self.activate)
43
+
44
+ def bind(self, identifier, singleton=True):
45
+ self.container.bind(identifier, singleton)
46
+
47
+ def tick(self):
48
+ self.engine.tick()
49
+
50
+ def register(
51
+ self, message_class: Type[BaseMessage], system_fn: Callable, priority: int = 0
52
+ ):
53
+ self.engine.register(message_class, system_fn, priority)
54
+
55
+ def on_before_tick(self, hook: TickHook, front: bool = False):
56
+ self.engine.on_before_tick(hook, front)
57
+
58
+ def on_after_tick(self, hook: TickHook, front: bool = False):
59
+ self.engine.on_after_tick(hook, front)
60
+
61
+ @overload
62
+ def on_before_execute(
63
+ self,
64
+ message_type: Type[F],
65
+ hook: ExecuteHook[F],
66
+ front: bool = False,
67
+ ) -> None: ...
68
+
69
+ @overload
70
+ def on_before_execute(
71
+ self,
72
+ message_type: Type[H],
73
+ hook: ExecuteHook[list[H]],
74
+ front: bool = False,
75
+ ) -> None: ...
76
+
77
+ @overload
78
+ def on_before_execute(
79
+ self,
80
+ message_type: Type[BaseMessage],
81
+ hook: ExecuteHook[list[SingleMessage] | EventMessage],
82
+ front: bool = False,
83
+ ) -> None: ...
84
+
85
+ def on_before_execute(
86
+ self, message_type: Any, hook: Any, front: bool = False
87
+ ) -> None:
88
+ self.engine.on_before_execute(message_type, hook, front)
89
+
90
+ @overload
91
+ def on_after_execute(
92
+ self,
93
+ message_type: Type[F],
94
+ hook: ExecuteHook[F],
95
+ front: bool = False,
96
+ ) -> None: ...
97
+
98
+ @overload
99
+ def on_after_execute(
100
+ self,
101
+ message_type: Type[H],
102
+ hook: ExecuteHook[list[H]],
103
+ front: bool = False,
104
+ ) -> None: ...
105
+
106
+ @overload
107
+ def on_after_execute(
108
+ self,
109
+ message_type: Type[BaseMessage],
110
+ hook: ExecuteHook[list[SingleMessage] | EventMessage],
111
+ front: bool = False,
112
+ ) -> None: ...
113
+
114
+ def on_after_execute(
115
+ self, message_type: Any, hook: Any, front: bool = False
116
+ ) -> None:
117
+ self.engine.on_after_execute(message_type, hook, front)
118
+
119
+ def use_middleware(self, middleware: Middleware):
120
+ self.engine.use_middleware(middleware)
121
+
122
+ def use(self, plugin: ViridPlugin[O], options: O) -> ViridApp:
123
+ if plugin.name in installed_plugins:
124
+ MessageWriter.warn(
125
+ f"[Virid Plugin] Duplicate Installation: Plugin {plugin.name} has already been installed."
126
+ )
127
+ return self
128
+
129
+ try:
130
+ plugin.install(self, options)
131
+ installed_plugins.add(plugin.name)
132
+ except Exception as e:
133
+ MessageWriter.error(
134
+ e, f"[Virid Container] Activation Hook Failed: {plugin.name}"
135
+ )
136
+
137
+ return self
138
+
139
+
140
+ virid_app = ViridApp()
141
+
142
+
143
+ from .util import register_base_handlers
144
+
145
+ register_base_handlers(virid_app)
@@ -0,0 +1,56 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from typing import Type, TypeVar, Callable, Any, Optional
7
+ from .core.io import MessageWriter
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ class Container:
13
+ def __init__(self):
14
+ self._bindings: dict[Type[Any], tuple[Type[Any], bool]] = {}
15
+ self._singletons: dict[Type[Any], Any] = {}
16
+
17
+ def bind(self, identifier: Type[T], singleton: bool = False) -> None:
18
+ """
19
+ 注册绑定。
20
+ - to: 映射到的目标类。如果不传,默认绑定到自身(toSelf)
21
+ - singleton: 是否为单例。默认为 False (transient)
22
+ """
23
+ if getattr(identifier, "__virid_component__", False) is True or getattr(identifier, "__virid_controller__", False) is True: # type: ignore
24
+ self._bindings[identifier] = (identifier, singleton)
25
+
26
+ else:
27
+ MessageWriter.error(
28
+ TypeError(
29
+ f"[Virid Container] Invalid Component: The Class {identifier.__name__} whit not @Component decorator."
30
+ )
31
+ )
32
+
33
+ def get(self, identifier: Type[T], on_activate: list[Callable]) -> T:
34
+ """获取实例,执行激活流水线"""
35
+ if identifier not in self._bindings:
36
+ raise RuntimeError(
37
+ f"[Virid Container] Unbound: No binding found for {identifier.__name__}"
38
+ )
39
+
40
+ target_ctor, is_singleton = self._bindings[identifier]
41
+
42
+ # 如果是单例
43
+ if is_singleton:
44
+ if identifier not in self._singletons:
45
+ self._singletons[identifier] = self.activate(target_ctor(), on_activate)
46
+ return self._singletons[identifier]
47
+
48
+ # 如果是多例
49
+ return self.activate(target_ctor(), on_activate)
50
+
51
+ def activate(self, component: Any, on_activate: list[Callable]):
52
+ result = component
53
+ for hook in on_activate:
54
+ result = hook(result)
55
+
56
+ return result
@@ -0,0 +1,10 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from .io import *
7
+ from .engine import *
8
+ from .message import *
9
+ from .registry import *
10
+ from .interface import *
@@ -0,0 +1,281 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+
7
+ import time
8
+ from typing import Callable, Type
9
+ from .interface import (
10
+ SystemTask,
11
+ TickHook,
12
+ ExecuteHook,
13
+ TickHookContext,
14
+ ExecuteHookContext,
15
+ )
16
+ from .io import MessageWriter
17
+ from .message import BaseMessage, SingleMessage, EventMessage
18
+ from .stage import Stage
19
+
20
+
21
+ class ExecutionTask:
22
+
23
+ def __init__(
24
+ self,
25
+ system_fn: Callable,
26
+ message: EventMessage | list[SingleMessage],
27
+ priority: int,
28
+ hook_context: ExecuteHookContext,
29
+ ):
30
+ self.message = message
31
+ self.system_fn = system_fn
32
+ self.priority = priority
33
+ self.hook_context = hook_context
34
+
35
+ def trigger_hook(
36
+ self,
37
+ hooks: list[
38
+ tuple[
39
+ type[BaseMessage],
40
+ ExecuteHook,
41
+ ]
42
+ ],
43
+ success: bool,
44
+ ):
45
+ # 根据是不是数组来取self.message的第一个
46
+ message = self.message[0] if isinstance(self.message, list) else self.message
47
+ try:
48
+ for hook in hooks:
49
+ if isinstance(message, hook[0]):
50
+ hook[1](self.message, self.hook_context, success)
51
+ except Exception as e:
52
+ MessageWriter.error(e, f"[Virid Hook] System Execute Hook Error.\n")
53
+
54
+ def execute(
55
+ self,
56
+ before_execute_hooks: list[
57
+ tuple[
58
+ type[BaseMessage],
59
+ ExecuteHook,
60
+ ]
61
+ ],
62
+ after_execute_hooks: list[
63
+ tuple[
64
+ type[BaseMessage],
65
+ ExecuteHook,
66
+ ]
67
+ ],
68
+ ):
69
+ success = True
70
+ self.trigger_hook(before_execute_hooks, success)
71
+ try:
72
+ self.system_fn(self.message)
73
+ except Exception as e:
74
+ success = False
75
+ self.trigger_hook(after_execute_hooks, success)
76
+ # 重新丢出错误
77
+ raise e
78
+
79
+ self.trigger_hook(after_execute_hooks, success)
80
+
81
+
82
+ class Dispatcher:
83
+ def __init__(self):
84
+ self.stage = Stage()
85
+ self.event_queue: list[EventMessage] = []
86
+ self.dirty_signal_types: dict[Type[SingleMessage], None] = {}
87
+
88
+ self.is_running = False
89
+ self.internal_depth = 0
90
+ self.tick_counter = 0
91
+
92
+ # 两个tick hook
93
+ self.before_tick_hooks: list[TickHook] = []
94
+ self.after_tick_hooks: list[TickHook] = []
95
+ self.tick_payload = {}
96
+
97
+ # 两个execute hook
98
+ self.before_execute_hooks: list[
99
+ tuple[
100
+ type[BaseMessage],
101
+ ExecuteHook,
102
+ ]
103
+ ] = []
104
+ self.after_execute_hooks: list[
105
+ tuple[
106
+ type[BaseMessage],
107
+ ExecuteHook,
108
+ ]
109
+ ] = []
110
+
111
+ def add_before_tick_hook(self, hook: TickHook, front: bool = False):
112
+ if front:
113
+ self.before_tick_hooks.insert(0, hook)
114
+ else:
115
+ self.before_tick_hooks.append(hook)
116
+
117
+ def add_after_tick_hook(self, hook: TickHook, front: bool = False):
118
+ if front:
119
+ self.after_tick_hooks.insert(0, hook)
120
+ else:
121
+ self.after_tick_hooks.append(hook)
122
+
123
+ def add_before_execute_hook(
124
+ self, message_type: Type[BaseMessage], hook: ExecuteHook, front: bool = False
125
+ ):
126
+ if front:
127
+ self.before_execute_hooks.insert(0, (message_type, hook))
128
+ else:
129
+ self.before_execute_hooks.append((message_type, hook))
130
+
131
+ def add_after_execute_hook(
132
+ self, message_type: Type[BaseMessage], hook: ExecuteHook, front: bool = False
133
+ ):
134
+ if front:
135
+ self.after_execute_hooks.insert(0, (message_type, hook))
136
+ else:
137
+ self.after_execute_hooks.append((message_type, hook))
138
+
139
+ def mark_dirty(self, message: BaseMessage):
140
+ self.stage.push(message)
141
+ # 根据消息类型放进不同的池子里
142
+ if isinstance(message, SingleMessage):
143
+ self.dirty_signal_types[type(message)] = None
144
+ elif isinstance(message, EventMessage):
145
+ self.event_queue.append(message)
146
+
147
+ def tick(self, system_task_map: dict[Type[BaseMessage], list[SystemTask]]):
148
+
149
+ if self.is_running or (
150
+ len(self.dirty_signal_types) == 0 and len(self.event_queue) == 0
151
+ ):
152
+ return
153
+
154
+ if self.internal_depth == 0:
155
+ self.tick_payload = {}
156
+ self.execute_hooks(self.before_tick_hooks)
157
+
158
+ if self.internal_depth > 100:
159
+ self.internal_depth = 0
160
+ self.dirty_signal_types.clear()
161
+ self.event_queue.clear()
162
+ self.stage.reset()
163
+ MessageWriter.error(
164
+ RuntimeError(
165
+ "[Virid Dispatcher] Internal depth exceeded 100. Possible infinite loop detected. 💥."
166
+ ),
167
+ )
168
+ return
169
+
170
+ self.is_running = True
171
+ self.internal_depth += 1
172
+
173
+ # 正式开始任务
174
+ signal_snapshot = set()
175
+ event_snapshot = list()
176
+
177
+ try:
178
+ signal_snapshot, event_snapshot = self.prepare_snapshot()
179
+ tasks = self.collect_tasks(
180
+ event_snapshot,
181
+ signal_snapshot,
182
+ system_task_map,
183
+ )
184
+ self.execute_tasks(tasks)
185
+
186
+ except Exception as e:
187
+ MessageWriter.error(e)
188
+
189
+ finally:
190
+ self.clear()
191
+ self.is_running = False
192
+ if len(self.dirty_signal_types) > 0 or len(self.event_queue) > 0:
193
+ self.tick(system_task_map)
194
+ else:
195
+ self.internal_depth = 0
196
+ self.execute_hooks(self.after_tick_hooks)
197
+ self.tick_counter += 1
198
+
199
+ def collect_tasks(
200
+ self,
201
+ event_snapshot: list[EventMessage],
202
+ signal_snapshot: list[Type[SingleMessage]],
203
+ system_task_map: dict[Type[BaseMessage], list[SystemTask]],
204
+ ):
205
+ tasks: list[ExecutionTask] = []
206
+ # 处理Event消息
207
+ for msg in event_snapshot:
208
+ for system_task in system_task_map.get(type(msg), []):
209
+ tasks.append(
210
+ ExecutionTask(
211
+ system_task.system_fn,
212
+ msg,
213
+ system_task.priority,
214
+ ExecuteHookContext(
215
+ tick=self.tick_counter,
216
+ context=system_task.system_fn.system_context, # type: ignore
217
+ payload={},
218
+ ),
219
+ )
220
+ )
221
+
222
+ # 处理Signal消息
223
+ for msg_cls in signal_snapshot:
224
+ for system_task in system_task_map.get(msg_cls, []):
225
+ tasks.append(
226
+ ExecutionTask(
227
+ system_task.system_fn,
228
+ self.stage.peek_signal(msg_cls),
229
+ system_task.priority,
230
+ ExecuteHookContext(
231
+ tick=self.tick_counter,
232
+ context=system_task.system_fn.system_context, # type: ignore
233
+ payload={},
234
+ ),
235
+ )
236
+ )
237
+
238
+ return tasks
239
+
240
+ def execute_tasks(
241
+ self,
242
+ tasks: list[ExecutionTask],
243
+ ):
244
+ # 按照优先级排序
245
+ tasks.sort(key=lambda task: task.priority, reverse=True)
246
+
247
+ for task in tasks:
248
+ try:
249
+ task.execute(self.before_execute_hooks, self.after_execute_hooks)
250
+ except Exception as e:
251
+ MessageWriter.error(
252
+ e,
253
+ f"[virid Dispatcher]: System Error. \n"
254
+ + f"SystemLocation: {task.system_fn.__name__} \n"
255
+ + f"MessageName: {type(task.message).__name__} \n"
256
+ + f"MessageData: {task.message} \n",
257
+ )
258
+
259
+ def prepare_snapshot(self) -> tuple[list[type[SingleMessage]], list[EventMessage]]:
260
+ self.stage.flip()
261
+ signal_snapshot = list(self.dirty_signal_types.keys())
262
+ event_snapshot = self.event_queue[:]
263
+ self.dirty_signal_types.clear()
264
+ self.event_queue.clear()
265
+ return signal_snapshot, event_snapshot
266
+
267
+ def clear(
268
+ self,
269
+ ):
270
+ self.stage.clear_signal()
271
+ self.stage.clear_event()
272
+
273
+ def execute_hooks(self, tick_hooks: list[TickHook]):
274
+ hooks_context = TickHookContext(
275
+ tick=self.tick_counter, time=time.time(), payload=self.tick_payload
276
+ )
277
+ try:
278
+ for hook in tick_hooks:
279
+ hook(hooks_context)
280
+ except Exception as e:
281
+ MessageWriter.error(e, f"[Virid Dispatcher]: Tick Hook Error.\n")
@@ -0,0 +1,83 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from typing import Callable, Type
7
+ from .message import BaseMessage
8
+ from .io import MessageWriter, activate_publisher
9
+ from .registry import Registry
10
+ from .dispatcher import Dispatcher
11
+ from .interface import TickHook, ExecuteHook, Middleware
12
+
13
+
14
+ class Engine:
15
+ def __init__(self):
16
+ self.registry = Registry()
17
+ self.dispatcher = Dispatcher()
18
+ self.middlewares: list[Middleware] = []
19
+ activate_publisher(self)
20
+
21
+ def use_middleware(self, middleware: Middleware):
22
+ self.middlewares.append(middleware)
23
+
24
+ def dispatch(self, message: BaseMessage):
25
+ """将消息放入缓冲区,等待调度器处理。"""
26
+
27
+ # 检查消息类型
28
+ if not isinstance(message, BaseMessage):
29
+ MessageWriter.error(
30
+ TypeError(
31
+ f"[Virid Dispatch] TypeError: Only instances of BaseMessage can be dispatched, got: {type(message).__name__}"
32
+ )
33
+ )
34
+
35
+ def final_action() -> None:
36
+ # 检查是否有对应的处理函数
37
+ message_class = type(message)
38
+ if message_class not in self.registry.system_task_map:
39
+ MessageWriter.warn(
40
+ f"[Virid Dispatch] No System Found: No system function is registered for message: {message_class.__name__}"
41
+ )
42
+
43
+ self.dispatcher.mark_dirty(message)
44
+
45
+ self.pipeline(message, final_action)
46
+
47
+ def tick(self):
48
+ """调度器调用,处理缓冲区中的消息。"""
49
+ self.dispatcher.tick(self.registry.system_task_map)
50
+
51
+ def register(
52
+ self,
53
+ message_class: Type[BaseMessage],
54
+ system_fn: Callable,
55
+ priority: int = 0,
56
+ ):
57
+ """注册System并返回一个无参的卸载函数。"""
58
+ return self.registry.register(message_class, system_fn, priority)
59
+
60
+ def pipeline(self, message: BaseMessage, final_action: Callable[[], None]):
61
+ def next_step(idx):
62
+ if idx < len(self.middlewares):
63
+ self.middlewares[idx](message, lambda: next_step(idx + 1))
64
+ else:
65
+ final_action()
66
+
67
+ next_step(0)
68
+
69
+ def on_before_tick(self, hook: TickHook, front: bool = False):
70
+ self.dispatcher.add_before_tick_hook(hook, front)
71
+
72
+ def on_after_tick(self, hook: TickHook, front: bool = False):
73
+ self.dispatcher.add_after_tick_hook(hook, front)
74
+
75
+ def on_before_execute(
76
+ self, message_type: Type[BaseMessage], hook: ExecuteHook, front: bool = False
77
+ ):
78
+ self.dispatcher.add_before_execute_hook(message_type, hook, front)
79
+
80
+ def on_after_execute(
81
+ self, message_type: Type[BaseMessage], hook: ExecuteHook, front: bool = False
82
+ ):
83
+ self.dispatcher.add_after_execute_hook(message_type, hook, front)
@@ -0,0 +1,59 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable, TypeVar
8
+ from .message import BaseMessage
9
+ T = TypeVar("T")
10
+
11
+ @dataclass
12
+ class SystemContext:
13
+ """
14
+ 系统上下文,包含系统执行时的相关信息
15
+ """
16
+
17
+ params: list[Any]
18
+ message_type: type[BaseMessage]
19
+ method_name: str
20
+ original_method: Callable
21
+
22
+
23
+ @dataclass
24
+ class SystemTask:
25
+ """
26
+ 系统任务,包含系统函数和相关信息
27
+ """
28
+
29
+ system_fn: Callable
30
+ priority: int
31
+
32
+
33
+ @dataclass
34
+ class TickHookContext:
35
+ """
36
+ TickHook 上下文,包含 tickHook 的相关信息
37
+ """
38
+
39
+ tick: int
40
+ time: float
41
+ payload: dict[str, Any]
42
+
43
+
44
+ @dataclass
45
+ class ExecuteHookContext:
46
+ """
47
+ ExecuteHook 上下文,包含 executeHook 的相关信息
48
+ """
49
+
50
+ tick: int
51
+ context: SystemContext
52
+ payload: dict[str, Any]
53
+
54
+
55
+ ExecuteHook = Callable[[T, ExecuteHookContext, bool], None]
56
+
57
+ TickHook = Callable[[TickHookContext], None]
58
+
59
+ Middleware = Callable[[BaseMessage, Callable[[], None]], None]
@@ -0,0 +1,62 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from typing import Type, TypeVar, Any
7
+ from .message import BaseMessage, ErrorMessage, WarnMessage, InfoMessage
8
+
9
+ T = TypeVar("T", bound=BaseMessage)
10
+
11
+
12
+ publisher: Any = None
13
+
14
+
15
+ def activate_publisher(dispatcher_instance: Any):
16
+ """供Dispatcher初始化时调用,延迟初始化"""
17
+ global publisher
18
+ publisher = dispatcher_instance
19
+
20
+
21
+ def publish(message: BaseMessage) -> None:
22
+ if publisher is None:
23
+ raise RuntimeError(
24
+ "[Virid MessageWriter] RuntimeError: No active dispatcher found. Please ensure that a dispatcher is activated before dispatching messages."
25
+ )
26
+ publisher.dispatch(message)
27
+
28
+
29
+ class MessageWriter:
30
+ """
31
+ 消息写入器。
32
+ """
33
+
34
+ @staticmethod
35
+ def write(target: Type[T] | T, *args, **kwargs) -> None:
36
+ """
37
+ 核心写入入口
38
+ """
39
+ if isinstance(target, type) and issubclass(target, BaseMessage):
40
+ # 动态实例化,完美透传所有位置参数与关键字参数
41
+ instance = target(*args, **kwargs)
42
+ elif isinstance(target, BaseMessage):
43
+ instance = target
44
+ else:
45
+ raise TypeError(
46
+ f"[Virid MessageWriter] TypeError: Only BaseMessage subclasses or instances are allowed to be written, got: {type(target).__name__}"
47
+ )
48
+
49
+ # 统一收拢到惰性分发代理
50
+ publish(instance)
51
+
52
+ @staticmethod
53
+ def error(error: Exception, context: str = "") -> None:
54
+ MessageWriter.write(ErrorMessage(error=error, context=context))
55
+
56
+ @staticmethod
57
+ def warn(context: str) -> None:
58
+ MessageWriter.write(WarnMessage(context=context))
59
+
60
+ @staticmethod
61
+ def info(context: str) -> None:
62
+ MessageWriter.write(InfoMessage(context=context))
@@ -0,0 +1,46 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from dataclasses import dataclass
7
+ from typing import TypeVar, Callable, ParamSpec
8
+
9
+ # 定义参数规格变量和类型变量
10
+ P = ParamSpec("P")
11
+ T = TypeVar("T")
12
+
13
+
14
+ @dataclass
15
+ class BaseMessage:
16
+ @classmethod
17
+ def send(cls: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> None:
18
+ # 这里的 cls 实际上就是构造函数本身
19
+ instance = cls(*args, **kwargs)
20
+ from .io import MessageWriter
21
+
22
+ MessageWriter.write(instance) # type: ignore
23
+
24
+
25
+ @dataclass
26
+ class SingleMessage(BaseMessage): ...
27
+
28
+
29
+ @dataclass
30
+ class EventMessage(BaseMessage): ...
31
+
32
+
33
+ @dataclass
34
+ class InfoMessage(EventMessage):
35
+ context: str
36
+
37
+
38
+ @dataclass
39
+ class WarnMessage(EventMessage):
40
+ context: str
41
+
42
+
43
+ @dataclass
44
+ class ErrorMessage(EventMessage):
45
+ error: Exception
46
+ context: str = ""
@@ -0,0 +1,63 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from typing import Type, Callable
7
+ from .message import BaseMessage
8
+ from .io import MessageWriter
9
+ from .interface import SystemTask
10
+
11
+
12
+ class Registry:
13
+ def __init__(self):
14
+ self.system_task_map: dict[Type[BaseMessage], list[SystemTask]] = {}
15
+
16
+ def register(
17
+ self,
18
+ message_class: Type[BaseMessage],
19
+ system_fn: Callable,
20
+ priority: int = 0,
21
+ ) -> Callable[[], None]:
22
+ """
23
+ 注册系统函数并返回一个无参的卸载函数。
24
+ """
25
+ # 初始化消息桶
26
+ if message_class not in self.system_task_map:
27
+ self.system_task_map[message_class] = []
28
+
29
+ systems = self.system_task_map[message_class]
30
+
31
+ # 检查重复注册
32
+ if system_fn in systems:
33
+ func_name = getattr(system_fn, "__name__", "Anonymous")
34
+ MessageWriter.warn(
35
+ f"[virid Warn] System function is already registered for this message type!\n"
36
+ f"Message Class: {message_class.__name__}\n"
37
+ f"Function Name: {func_name}\n"
38
+ )
39
+ return lambda: None
40
+
41
+ # 追加注册到桶中
42
+ systems.append(SystemTask(system_fn=system_fn, priority=priority))
43
+
44
+ # 返回一个闭包卸载函数
45
+ def unregister() -> None:
46
+ current_systems = self.system_task_map.get(message_class)
47
+ if current_systems is not None:
48
+ # 寻找匹配的系统索引(对标 JS 的 findIndex)
49
+ index = next(
50
+ (
51
+ i
52
+ for i, s in enumerate(current_systems)
53
+ if s.system_fn == system_fn
54
+ ),
55
+ -1,
56
+ )
57
+ if index != -1:
58
+ current_systems.pop(index)
59
+ # 如果这个消息类型没有任何系统监听了,彻底删除 key,保持内存干净
60
+ if len(current_systems) == 0:
61
+ del self.system_task_map[message_class]
62
+
63
+ return unregister
@@ -0,0 +1,75 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from typing import Type
7
+ from .message import BaseMessage, SingleMessage, EventMessage
8
+ from .io import MessageWriter
9
+
10
+
11
+ class Stage:
12
+ def __init__(self):
13
+ # SingleMessage缓冲池
14
+ self._signal_active: dict[Type[SingleMessage], list[SingleMessage]] = {}
15
+ self._signal_staging: dict[Type[SingleMessage], list[SingleMessage]] = {}
16
+
17
+ # EventMessage缓冲池
18
+ self._event_active: list[EventMessage] = []
19
+ self._event_staging: list[EventMessage] = []
20
+
21
+ def push(self, event: BaseMessage):
22
+ """根据消息继承范式,物理隔离分流"""
23
+ if isinstance(event, SingleMessage):
24
+ msg_cls = type(event)
25
+ if msg_cls not in self._signal_staging:
26
+ self._signal_staging[msg_cls] = []
27
+ self._signal_staging[msg_cls].append(event)
28
+
29
+ elif isinstance(event, EventMessage):
30
+ self._event_staging.append(event)
31
+
32
+ else:
33
+ MessageWriter.error(
34
+ TypeError(
35
+ f"[Virid Buffer] TypeError: Message {type(event).__name__} must inherit from SingleMessage or EventMessage"
36
+ )
37
+ )
38
+
39
+ def flip(self):
40
+ """
41
+ 翻转双缓冲区:
42
+ """
43
+ # 物理隔离:Active 指向当前轮的快照,Staging 重置为全新容器
44
+ self._signal_active = self._signal_staging
45
+ self._signal_staging = {}
46
+
47
+ self._event_active = self._event_staging
48
+ self._event_staging = []
49
+
50
+ def peek_signal(self, msg_cls: Type[SingleMessage]) -> list[SingleMessage]:
51
+ """
52
+ 获取指定消息类型的缓冲池中的消息
53
+ """
54
+ return self._signal_active.get(msg_cls, [])
55
+
56
+ def clear_signal(self) -> None:
57
+ """
58
+ 清空指定消息类型的缓冲池
59
+ """
60
+ self._signal_active.clear()
61
+
62
+ def clear_event(self) -> None:
63
+ """
64
+ 清空事件消息缓冲池
65
+ """
66
+ self._event_active.clear()
67
+
68
+ def reset(self):
69
+ """
70
+ 重置整个缓冲区,清空所有消息
71
+ """
72
+ self._signal_active = {}
73
+ self._signal_staging = {}
74
+ self._event_active = []
75
+ self._event_staging = []
@@ -0,0 +1,6 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ from .decorator import *
@@ -0,0 +1,208 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+ import inspect
7
+ from functools import wraps
8
+ from typing import Any, Callable, Type, Optional, get_origin, get_args
9
+
10
+ from ..core.interface import SystemContext
11
+ from ..core.message import BaseMessage, EventMessage, SingleMessage
12
+ from ..core.io import MessageWriter
13
+ from ..app import virid_app
14
+
15
+
16
+ def handle_result(res: Any) -> None:
17
+ """统一处理返回值,支持链式反应,平铺列表投递"""
18
+ if res is None:
19
+ return
20
+ # 如果返回的是列表/元组,平铺处理
21
+ messages = res if isinstance(res, (list, tuple)) else [res]
22
+
23
+ for m in messages:
24
+ if isinstance(m, BaseMessage):
25
+ MessageWriter.write(m)
26
+ else:
27
+ MessageWriter.warn(
28
+ f"[virid HandleResult] Invalid Return Type: Expected BaseMessage or List[BaseMessage], got {type(m).__name__}. Ignored."
29
+ )
30
+
31
+
32
+ def system(
33
+ message_type: Optional[Type[BaseMessage]] = None,
34
+ priority: int = 0,
35
+ singleton: bool = True,
36
+ ):
37
+ """
38
+ 系统装饰器
39
+ :param message_type: 监听的消息类型 (可选,如果不传会自动从参数的类型注解推断)
40
+ :param priority: 优先级,数值越大越早执行
41
+ :param singleton: 是否为单例模式。True 传入单条消息;False 传入整个消息列表
42
+ """
43
+
44
+ # 提前校验 decorator 参数
45
+ if message_type is not None and not (
46
+ isinstance(message_type, type) and issubclass(message_type, BaseMessage)
47
+ ):
48
+ raise TypeError(
49
+ f"[Virid System] TypeError: @System requires a subclass of BaseMessage, got: {message_type}"
50
+ )
51
+
52
+ def decorator(func: Callable) -> Callable:
53
+ sig = inspect.signature(func)
54
+ params = list(sig.parameters.values())
55
+
56
+ if not params:
57
+ raise ValueError(
58
+ f"[Virid System] Parameter Loss: '{func.__name__}' must have at least one parameter for receiving messages!"
59
+ )
60
+
61
+ # 支持泛型解包的参数解析器
62
+ msg_param_name = None
63
+ inferred_msg_type = None
64
+
65
+ for param in params:
66
+ annotation = param.annotation
67
+ if not annotation or annotation == inspect.Parameter.empty:
68
+ continue
69
+
70
+ target_type = None
71
+ origin = get_origin(annotation)
72
+
73
+ # 如果是泛型列表(如 list[DecreaseMessage] 或 List[DecreaseMessage])
74
+ if origin is list:
75
+ args = get_args(annotation)
76
+ if (
77
+ args
78
+ and isinstance(args[0], type)
79
+ and issubclass(args[0], BaseMessage)
80
+ ):
81
+ target_type = args[0] # 提取出里面的 DecreaseMessage 真实类型
82
+
83
+ # 普通类型
84
+ elif isinstance(annotation, type) and issubclass(annotation, BaseMessage):
85
+ target_type = annotation
86
+
87
+ # 如果找到了消息参数
88
+ if target_type is not None:
89
+ if msg_param_name is not None:
90
+ raise ValueError(
91
+ f"[Virid System] Multiple Messages: '{func.__name__}' cannot have multiple BaseMessage parameters."
92
+ )
93
+ msg_param_name = param.name
94
+ inferred_msg_type = target_type
95
+
96
+ # 确定最终的消息类型 (装饰器明确指定优先 > 参数类型推断)
97
+ final_message_type = message_type or inferred_msg_type
98
+
99
+ if final_message_type is None:
100
+ raise ValueError(
101
+ f"[Virid System] Parameter Loss: Cannot infer message type for '{func.__name__}'. "
102
+ f"Declare via @System(MyMessage) or type hint (e.g., def my_system(msg: MyMessage):)."
103
+ )
104
+
105
+ # 运行时警告:类型冲突
106
+ if (
107
+ message_type
108
+ and inferred_msg_type
109
+ and not issubclass(inferred_msg_type, message_type)
110
+ ):
111
+ MessageWriter.warn(
112
+ f"[virid System] Type Mismatch in '{func.__name__}': Decorator expects {message_type.__name__}, "
113
+ f"but parameter expects {inferred_msg_type.__name__}."
114
+ )
115
+
116
+ # 闭包缓存变量
117
+ is_initialized = False
118
+ cached_components: list[Any] = [None] * len(params)
119
+
120
+ # 根据 singleton 开关,精准重组参数负载
121
+ def build_args(message: Any) -> list:
122
+ """构建函数入参:将消息实体和可能存在的依赖注入按顺序排好"""
123
+ nonlocal is_initialized, cached_components
124
+
125
+ is_incoming_list = isinstance(message, list)
126
+
127
+ # 加工最终要塞给函数对应的参数负载
128
+ if singleton:
129
+ # 单例模式:函数只想要单条消息
130
+ true_message = message[-1] if is_incoming_list else message
131
+
132
+ # 运行时类型校验
133
+ if not isinstance(true_message, final_message_type):
134
+ raise TypeError(
135
+ f"[virid System] Type Mismatch: Expected {final_message_type.__name__}, but received {type(true_message).__name__}"
136
+ )
137
+ payload = true_message
138
+ else:
139
+ # 批处理模式:函数想要一个消息列表
140
+ true_messages_list = message if is_incoming_list else [message]
141
+
142
+ # 运行时对列表内的元素进行类型校验
143
+ if true_messages_list and not isinstance(
144
+ true_messages_list[0], final_message_type
145
+ ):
146
+ raise TypeError(
147
+ f"[virid System] Type Mismatch: Expected elements of {final_message_type.__name__}, but received {type(true_messages_list[0]).__name__}"
148
+ )
149
+ payload = true_messages_list
150
+
151
+ # 首次命中去容器捞取并缓存所有 Component 单例
152
+ if not is_initialized:
153
+ for idx, param in enumerate(params):
154
+ if param.name != msg_param_name:
155
+ # 只有在第一次运行时,才去全局容器里捞
156
+ inject_instance = virid_app.get(param.annotation)
157
+ if inject_instance is None:
158
+ raise RuntimeError(
159
+ f"[virid System] Unknown Inject Data Types: '{param.name}' ({param.annotation.__name__}) "
160
+ f"is not registered in the container for '{func.__name__}'!"
161
+ )
162
+ cached_components[idx] = inject_instance
163
+ # 标记初始化完成
164
+ is_initialized = True
165
+
166
+ # 高频重组参数
167
+ call_args = []
168
+ for idx, param in enumerate(params):
169
+ if param.name == msg_param_name:
170
+ call_args.append(payload)
171
+ else:
172
+ # 直接从闭包里的数组中拿
173
+ call_args.append(cached_components[idx])
174
+
175
+ return call_args
176
+
177
+ # 包装函数 (同步)
178
+ @wraps(func)
179
+ def wrapped_system(message: EventMessage | list[SingleMessage]):
180
+ # 动态组装参数
181
+ call_args = build_args(message)
182
+ result = func(*call_args)
183
+ # 解析并处理返回值(支持链式反应)
184
+ handle_result(result)
185
+ return result
186
+
187
+ # 给包装后的函数挂载上下文信息
188
+ wrapped_system.system_context: SystemContext = { # type: ignore
189
+ "params": [p.annotation for p in params],
190
+ "message_type": final_message_type,
191
+ "method_name": func.__name__,
192
+ "original_method": func,
193
+ }
194
+
195
+ # 自动向全局调度中心登记
196
+ virid_app.register(final_message_type, wrapped_system, priority)
197
+
198
+ return wrapped_system
199
+
200
+ return decorator
201
+
202
+
203
+ def component():
204
+ def bind_component(cls):
205
+ cls.__virid_component__ = True
206
+ return cls
207
+
208
+ return bind_component
@@ -0,0 +1,111 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+
7
+ from .core.message import ErrorMessage, InfoMessage, WarnMessage
8
+ from .core.interface import SystemContext
9
+ from logging import getLogger
10
+ import logging
11
+
12
+ switch = True
13
+
14
+
15
+ def toggle_utils(val: bool) -> None:
16
+ global switch
17
+ switch = val
18
+
19
+
20
+ class ViridFormatter(logging.Formatter):
21
+ # 颜色与样式转义码
22
+ RESET = "\x1b[0m"
23
+ BOLD = "\x1b[1m"
24
+ RED = "\x1b[31m"
25
+ GREEN = "\x1b[32m"
26
+ YELLOW = "\x1b[33m"
27
+ MAGENTA = "\x1b[35m"
28
+ CYAN = "\x1b[36m"
29
+ GRAY = "\x1b[90m"
30
+
31
+ def format(self, record):
32
+ if record.levelno == logging.INFO:
33
+ header = f"{self.GREEN}{self.BOLD} ✔ [Virid Info] {self.RESET}"
34
+ record.msg = (
35
+ f"{header}{self.GRAY}Global Info Caught:{self.RESET}\n"
36
+ f" {self.GREEN}Details:{self.RESET} {record.msg}"
37
+ )
38
+ elif record.levelno == logging.WARNING:
39
+ header = f"{self.YELLOW}{self.BOLD} ⚠ [Virid Warn] {self.RESET}"
40
+ context = f"{self.CYAN}{record.msg}{self.RESET}"
41
+ record.msg = (
42
+ f"{header}{self.GRAY}Global Warn Caught:{self.RESET}\n"
43
+ f" {self.YELLOW}Context:{self.RESET} {context}"
44
+ )
45
+ elif record.levelno == logging.ERROR:
46
+ header = f"{self.RED}{self.BOLD} ✖ [Virid Error] {self.RESET}"
47
+
48
+ if "context" in getattr(record, "msg_type", ""):
49
+ record.msg = f" {self.RED}Context:{self.RESET} {self.MAGENTA}{record.msg}{self.RESET}"
50
+ else:
51
+ record.msg = (
52
+ f"{header}{self.GRAY}Global Error Caught:{self.RESET}\n"
53
+ f" {self.RED}Details:{self.RESET} {record.msg}"
54
+ )
55
+
56
+ return super().format(record)
57
+
58
+
59
+ logging.basicConfig(level=logging.INFO)
60
+ handler = logging.root.handlers[0]
61
+ handler.setFormatter(ViridFormatter("%(message)s"))
62
+ logger = getLogger(__name__)
63
+
64
+
65
+ def error(message: ErrorMessage) -> None:
66
+ if switch is False:
67
+ return
68
+ logger.error(message.error, extra={"msg_type": "error"})
69
+ logger.error(message.context, extra={"msg_type": "context"})
70
+
71
+
72
+ error.system_context: SystemContext = { # type: ignore
73
+ "params": [ErrorMessage],
74
+ "message_type": ErrorMessage,
75
+ "method_name": error.__name__,
76
+ "original_method": error,
77
+ }
78
+
79
+
80
+ def info(message: InfoMessage) -> None:
81
+ if switch is False:
82
+ return
83
+ logger.info(message.context)
84
+
85
+
86
+ info.system_context: SystemContext = { # type: ignore
87
+ "params": [InfoMessage],
88
+ "message_type": InfoMessage,
89
+ "method_name": info.__name__,
90
+ "original_method": info,
91
+ }
92
+
93
+
94
+ def warn(message: WarnMessage) -> None:
95
+ if switch is False:
96
+ return
97
+ logger.warning(message.context)
98
+
99
+
100
+ warn.system_context: SystemContext = { # type: ignore
101
+ "params": [WarnMessage],
102
+ "message_type": WarnMessage,
103
+ "method_name": warn.__name__,
104
+ "original_method": warn,
105
+ }
106
+
107
+
108
+ def register_base_handlers(virid) -> None:
109
+ virid.register(ErrorMessage, error)
110
+ virid.register(InfoMessage, info)
111
+ virid.register(WarnMessage, warn)
@@ -0,0 +1,75 @@
1
+ """
2
+ Copyright (c) 2026-present Ailrid.
3
+ Licensed under the Apache License, Version 2.0.
4
+ Project: Virid
5
+ """
6
+
7
+ import time
8
+ from dataclasses import dataclass
9
+ from virid.core import (
10
+ component,
11
+ EventMessage,
12
+ system,
13
+ create_virid,
14
+ )
15
+
16
+ virid = create_virid()
17
+
18
+
19
+ @component()
20
+ @dataclass
21
+ class BenchCounter:
22
+ value: int = 0
23
+
24
+
25
+ virid.bind(BenchCounter)
26
+
27
+ class BenchMessage(EventMessage): ...
28
+
29
+
30
+ # 用来在外部验证系统确实执行了正确次数的全局计数器
31
+ actual_run_count = 0
32
+
33
+
34
+ @system(message_type=BenchMessage)
35
+ def benchmark(counter: BenchCounter) -> None:
36
+ global actual_run_count
37
+ counter.value += 1
38
+ actual_run_count += 1
39
+
40
+
41
+ if __name__ == "__main__":
42
+ # 压测规模:跑 200,000 次完整的 [Send -> Tick -> DI -> Execute] 大循环
43
+ TOTAL_ITERATIONS = 200_000
44
+
45
+ print("=" * 60)
46
+ print(" 开始 Virid 框架全链路集成压测 (Full Loop Benchmark)...")
47
+ print(f" 压测规模: {TOTAL_ITERATIONS:,} 次 完整框架调度闭环")
48
+ print("=" * 60)
49
+
50
+ # 预热第一帧
51
+ BenchMessage.send()
52
+ virid.tick()
53
+
54
+ # 重置计数器,开始正式全链路计时
55
+ actual_run_count = 0
56
+ start_time = time.perf_counter()
57
+
58
+ # 核心吞吐量高频循环
59
+ for _ in range(TOTAL_ITERATIONS):
60
+ BenchMessage.send()
61
+ virid.tick()
62
+
63
+ end_time = time.perf_counter()
64
+
65
+ # 性能结果统计
66
+ elapsed_time = end_time - start_time
67
+ throughput = TOTAL_ITERATIONS / elapsed_time
68
+
69
+ print(f"【压测结果】")
70
+ print(f" 完整跑完耗时 : {elapsed_time:.4f} 秒")
71
+ print(f" 框架全链路吞吐: {throughput:,.2f} 次全循环/秒 (Ticks/sec)")
72
+ print("-" * 60)
73
+ print(f"【确定性状态校验】")
74
+ print(f" 系统实际触发次数: {actual_run_count:,} 次 (预期: {TOTAL_ITERATIONS:,})")
75
+ print("=" * 60)