PyEventEngine 0.6.0.post3__cp312-cp312-win_amd64.whl
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.
- event_engine/__infra__.pxd +2 -0
- event_engine/__init__.pxd +2 -0
- event_engine/__init__.py +37 -0
- event_engine/base/__infra__.pxd +9 -0
- event_engine/base/__init__.pxd +9 -0
- event_engine/base/__init__.py +82 -0
- event_engine/base/c_allocator_protocol.c +10817 -0
- event_engine/base/c_allocator_protocol.cp312-win_amd64.pyd +0 -0
- event_engine/base/c_allocator_protocol.h +23 -0
- event_engine/base/c_allocator_protocol.pxd +31 -0
- event_engine/base/c_allocator_protocol.pyx +99 -0
- event_engine/capi/__infra__.pxd +189 -0
- event_engine/capi/__init__.pxd +189 -0
- event_engine/capi/__init__.py +84 -0
- event_engine/capi/c_engine.c +28711 -0
- event_engine/capi/c_engine.cp312-win_amd64.pyd +0 -0
- event_engine/capi/c_engine.h +393 -0
- event_engine/capi/c_engine.pxd +84 -0
- event_engine/capi/c_engine.pyi +424 -0
- event_engine/capi/c_engine.pyx +560 -0
- event_engine/capi/c_event.c +19921 -0
- event_engine/capi/c_event.cp312-win_amd64.pyd +0 -0
- event_engine/capi/c_event.h +342 -0
- event_engine/capi/c_event.pxd +159 -0
- event_engine/capi/c_event.pyi +226 -0
- event_engine/capi/c_event.pyx +387 -0
- event_engine/capi/c_topic.c +30978 -0
- event_engine/capi/c_topic.cp312-win_amd64.pyd +0 -0
- event_engine/capi/c_topic.h +1007 -0
- event_engine/capi/c_topic.pxd +147 -0
- event_engine/capi/c_topic.pyi +580 -0
- event_engine/capi/c_topic.pyx +725 -0
- event_engine/capi/fallback_engine.py +670 -0
- event_engine/includes/event_engine/base/c_allocator_protocol.c +10817 -0
- event_engine/includes/event_engine/base/c_allocator_protocol.h +23 -0
- event_engine/includes/event_engine/capi/c_engine.c +28711 -0
- event_engine/includes/event_engine/capi/c_engine.h +393 -0
- event_engine/includes/event_engine/capi/c_event.c +19921 -0
- event_engine/includes/event_engine/capi/c_event.h +342 -0
- event_engine/includes/event_engine/capi/c_topic.c +30978 -0
- event_engine/includes/event_engine/capi/c_topic.h +1007 -0
- event_engine/native/__init__.py +60 -0
- event_engine/native/_event.py +420 -0
- event_engine/native/_topic.py +171 -0
- event_engine/native/engine.py +746 -0
- event_engine/native/event.py +467 -0
- event_engine/native/topic.py +945 -0
- pyeventengine-0.6.0.post3.dist-info/METADATA +189 -0
- pyeventengine-0.6.0.post3.dist-info/RECORD +52 -0
- pyeventengine-0.6.0.post3.dist-info/WHEEL +5 -0
- pyeventengine-0.6.0.post3.dist-info/licenses/LICENSE +21 -0
- pyeventengine-0.6.0.post3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
from threading import Thread
|
|
2
|
+
|
|
3
|
+
from cpython.datetime cimport timedelta
|
|
4
|
+
from cpython.object cimport PyObject
|
|
5
|
+
from cpython.ref cimport Py_INCREF, Py_XDECREF, Py_XINCREF
|
|
6
|
+
from cpython.unicode cimport PyUnicode_FromStringAndSize
|
|
7
|
+
|
|
8
|
+
from cbase.bytemap.c_bytemap cimport bytemap_ret_code, bytemap, bytemap_entry, c_bytemap_new, c_bytemap_clear, c_bytemap_free, c_bytemap_get, c_bytemap_set, c_bytemap_pop, c_bytemap_entry_value
|
|
9
|
+
|
|
10
|
+
from .c_event cimport EMPTY_ARGS, MessagePayload, c_evt_hook_invoke, c_evt_payload_free, c_evt_payload_new, evt_py_payload
|
|
11
|
+
from .c_topic cimport c_topic_match_bool
|
|
12
|
+
from ..base.c_allocator_protocol cimport EE_HEAP_ALLOCATOR
|
|
13
|
+
from ..base import LOGGER
|
|
14
|
+
|
|
15
|
+
LOGGER = LOGGER.getChild('Engine')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Full(Exception):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Empty(Exception):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
cdef class EventEngine:
|
|
27
|
+
def __cinit__(self, size_t capacity=DEFAULT_MQ_CAPACITY, object logger=None):
|
|
28
|
+
self.logger = LOGGER.getChild(f'EventEngine') if logger is None else logger
|
|
29
|
+
cdef allocator_protocol* allocator = EE_HEAP_ALLOCATOR
|
|
30
|
+
|
|
31
|
+
self.mq = c_mq_new(capacity, NULL, allocator)
|
|
32
|
+
if not self.mq:
|
|
33
|
+
raise MemoryError(f'Failed to allocate MessageQueue for {self.__class__.__name__}.')
|
|
34
|
+
|
|
35
|
+
self.exact_topic_hooks = c_bytemap_new(0, NULL)
|
|
36
|
+
if not self.exact_topic_hooks:
|
|
37
|
+
c_mq_free(self.mq)
|
|
38
|
+
self.mq = NULL
|
|
39
|
+
raise MemoryError(f'Failed to allocate exact_topic_hooks for {self.__class__.__name__}.')
|
|
40
|
+
|
|
41
|
+
self.generic_topic_hooks = c_bytemap_new(0, NULL)
|
|
42
|
+
if not self.generic_topic_hooks:
|
|
43
|
+
c_mq_free(self.mq)
|
|
44
|
+
c_bytemap_free(self.exact_topic_hooks)
|
|
45
|
+
self.mq = NULL
|
|
46
|
+
raise MemoryError(f'Failed to allocate generic_topic_hooks for {self.__class__.__name__}.')
|
|
47
|
+
|
|
48
|
+
self.seq_id = 0
|
|
49
|
+
|
|
50
|
+
def __dealloc__(self):
|
|
51
|
+
if self.mq:
|
|
52
|
+
c_mq_free(self.mq)
|
|
53
|
+
self.mq = NULL
|
|
54
|
+
|
|
55
|
+
if self.exact_topic_hooks:
|
|
56
|
+
c_bytemap_free(self.exact_topic_hooks)
|
|
57
|
+
self.exact_topic_hooks = NULL
|
|
58
|
+
|
|
59
|
+
if self.generic_topic_hooks:
|
|
60
|
+
c_bytemap_free(self.generic_topic_hooks)
|
|
61
|
+
self.generic_topic_hooks = NULL
|
|
62
|
+
|
|
63
|
+
cdef inline void c_loop(self):
|
|
64
|
+
if not self.mq:
|
|
65
|
+
raise RuntimeError('Not initialized!')
|
|
66
|
+
|
|
67
|
+
cdef evt_message_payload* msg = NULL
|
|
68
|
+
cdef evt_py_payload* py_payload = NULL
|
|
69
|
+
cdef message_queue* mq = self.mq
|
|
70
|
+
cdef int ret_code
|
|
71
|
+
|
|
72
|
+
while self.active:
|
|
73
|
+
# Step 1: Await message
|
|
74
|
+
with nogil:
|
|
75
|
+
ret_code = c_mq_get_hybrid(mq, &msg, DEFAULT_MQ_SPIN_LIMIT, DEFAULT_MQ_TIMEOUT_SECONDS)
|
|
76
|
+
if ret_code != 0:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
# Trigger message callbacks
|
|
80
|
+
self.c_trigger(msg)
|
|
81
|
+
|
|
82
|
+
# Clean up the message payload
|
|
83
|
+
c_evt_payload_free(msg)
|
|
84
|
+
|
|
85
|
+
cdef inline evt_message_payload* c_get(self, bint block, size_t max_spin, double timeout):
|
|
86
|
+
cdef evt_message_payload* msg = NULL
|
|
87
|
+
cdef int ret_code
|
|
88
|
+
if block:
|
|
89
|
+
ret_code = c_mq_get_hybrid(self.mq, &msg, max_spin, timeout)
|
|
90
|
+
else:
|
|
91
|
+
ret_code = c_mq_get(self.mq, &msg)
|
|
92
|
+
|
|
93
|
+
if ret_code != 0:
|
|
94
|
+
return NULL
|
|
95
|
+
return msg
|
|
96
|
+
|
|
97
|
+
cdef inline int c_publish(self, Topic topic, tuple args, dict kwargs, bint block, size_t max_spin, double timeout):
|
|
98
|
+
if not topic.header.is_exact:
|
|
99
|
+
raise ValueError('Topic must be all of exact parts')
|
|
100
|
+
|
|
101
|
+
# Step 0: Request payload buffer (MUST be done with GIL held - allocator is NOT thread-safe)
|
|
102
|
+
cdef evt_message_payload* payload = c_evt_payload_new(topic, args, kwargs)
|
|
103
|
+
|
|
104
|
+
# Step 1: Assembling payload (MUST be done with GIL held - touching Python objects)
|
|
105
|
+
payload.seq_id = self.seq_id
|
|
106
|
+
self.seq_id += 1
|
|
107
|
+
|
|
108
|
+
# Step 2: Send the payload (can be done without GIL - queue is thread-safe)
|
|
109
|
+
cdef int ret_code
|
|
110
|
+
with nogil:
|
|
111
|
+
if block:
|
|
112
|
+
ret_code = c_mq_put_hybrid(self.mq, payload, max_spin, timeout)
|
|
113
|
+
else:
|
|
114
|
+
ret_code = c_mq_put(self.mq, payload)
|
|
115
|
+
|
|
116
|
+
# Step 4: Handle failure case (undo increfs and free payload)
|
|
117
|
+
if not ret_code:
|
|
118
|
+
return ret_code
|
|
119
|
+
|
|
120
|
+
self.seq_id -= 1
|
|
121
|
+
c_evt_payload_free(payload)
|
|
122
|
+
return ret_code
|
|
123
|
+
|
|
124
|
+
cdef inline void c_trigger(self, evt_message_payload* msg):
|
|
125
|
+
cdef evt_topic* msg_topic = msg.topic
|
|
126
|
+
# Step 1: Match exact_topic_hooks
|
|
127
|
+
cdef PyObject* hook_ptr = NULL
|
|
128
|
+
cdef EventHook event_hook
|
|
129
|
+
|
|
130
|
+
cdef int ret_code = c_bytemap_get(self.exact_topic_hooks, msg_topic.key, msg_topic.key_len, <void**> &hook_ptr)
|
|
131
|
+
if hook_ptr:
|
|
132
|
+
event_hook = <EventHook> hook_ptr
|
|
133
|
+
c_evt_hook_invoke(event_hook.header, msg)
|
|
134
|
+
|
|
135
|
+
# Step 2: Match generic_topic_hooks
|
|
136
|
+
cdef bytemap_entry* entry = self.generic_topic_hooks.first
|
|
137
|
+
cdef int is_matched
|
|
138
|
+
while entry:
|
|
139
|
+
hook_ptr = <PyObject*> c_bytemap_entry_value(entry)
|
|
140
|
+
if not hook_ptr:
|
|
141
|
+
entry = entry.next
|
|
142
|
+
continue
|
|
143
|
+
event_hook = <EventHook> <PyObject*> hook_ptr
|
|
144
|
+
is_matched = c_topic_match_bool(event_hook.topic.header, msg_topic)
|
|
145
|
+
if is_matched:
|
|
146
|
+
c_evt_hook_invoke(event_hook.header, msg)
|
|
147
|
+
entry = entry.next
|
|
148
|
+
|
|
149
|
+
cdef inline EventHook c_get_hook(self, Topic topic):
|
|
150
|
+
cdef evt_topic* topic_ptr = topic.header
|
|
151
|
+
cdef PyObject* hook_ptr = NULL
|
|
152
|
+
cdef EventHook event_hook
|
|
153
|
+
cdef bytemap* hook_map
|
|
154
|
+
|
|
155
|
+
if topic_ptr.is_exact:
|
|
156
|
+
hook_map = self.exact_topic_hooks
|
|
157
|
+
else:
|
|
158
|
+
hook_map = self.generic_topic_hooks
|
|
159
|
+
|
|
160
|
+
cdef int ret_code = c_bytemap_get(hook_map, topic_ptr.key, topic_ptr.key_len, <void**> &hook_ptr)
|
|
161
|
+
if ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
|
|
162
|
+
raise KeyError(f'No EventHook registered for {topic.value}')
|
|
163
|
+
event_hook = <EventHook> hook_ptr
|
|
164
|
+
Py_XINCREF(hook_ptr)
|
|
165
|
+
return event_hook
|
|
166
|
+
|
|
167
|
+
cdef inline void c_register_hook(self, EventHook hook):
|
|
168
|
+
cdef evt_topic* topic_ptr = hook.topic.header
|
|
169
|
+
cdef PyObject* existing_hook_ptr = NULL
|
|
170
|
+
cdef bytemap* hook_map
|
|
171
|
+
|
|
172
|
+
if topic_ptr.is_exact:
|
|
173
|
+
hook_map = self.exact_topic_hooks
|
|
174
|
+
else:
|
|
175
|
+
hook_map = self.generic_topic_hooks
|
|
176
|
+
|
|
177
|
+
cdef int ret_code = c_bytemap_get(hook_map, topic_ptr.key, topic_ptr.key_len, <void**> &existing_hook_ptr)
|
|
178
|
+
if existing_hook_ptr and existing_hook_ptr != <PyObject*> hook:
|
|
179
|
+
raise KeyError(f'Another EventHook already registered for {hook.topic.value}')
|
|
180
|
+
ret_code = c_bytemap_set(hook_map, topic_ptr.key, topic_ptr.key_len, <void*> <PyObject*> hook, NULL)
|
|
181
|
+
Py_INCREF(hook)
|
|
182
|
+
|
|
183
|
+
cdef inline EventHook c_unregister_hook(self, Topic topic):
|
|
184
|
+
cdef evt_topic* topic_ptr = topic.header
|
|
185
|
+
cdef PyObject* existing_hook_ptr = NULL
|
|
186
|
+
cdef bytemap* hook_map
|
|
187
|
+
|
|
188
|
+
if topic_ptr.is_exact:
|
|
189
|
+
hook_map = self.exact_topic_hooks
|
|
190
|
+
else:
|
|
191
|
+
hook_map = self.generic_topic_hooks
|
|
192
|
+
cdef int ret_code = c_bytemap_pop(hook_map, <char*> topic_ptr.key, topic_ptr.key_len, <void**> &existing_hook_ptr)
|
|
193
|
+
if not existing_hook_ptr:
|
|
194
|
+
raise KeyError(f'No EventHook registered for {topic.value}')
|
|
195
|
+
cdef EventHook hook = <EventHook> existing_hook_ptr
|
|
196
|
+
Py_XDECREF(<PyObject*> hook)
|
|
197
|
+
return hook
|
|
198
|
+
|
|
199
|
+
cdef inline void c_register_handler(self, Topic topic, object py_callable, bint deduplicate):
|
|
200
|
+
cdef evt_topic* topic_ptr = topic.header
|
|
201
|
+
cdef PyObject* hook_ptr = NULL
|
|
202
|
+
cdef EventHook event_hook
|
|
203
|
+
cdef bytemap* hook_map
|
|
204
|
+
|
|
205
|
+
if topic_ptr.is_exact:
|
|
206
|
+
hook_map = self.exact_topic_hooks
|
|
207
|
+
else:
|
|
208
|
+
hook_map = self.generic_topic_hooks
|
|
209
|
+
|
|
210
|
+
cdef int ret_code = c_bytemap_get(hook_map, topic_ptr.key, topic_ptr.key_len, <void**> &hook_ptr)
|
|
211
|
+
if ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
|
|
212
|
+
event_hook = EventHook.__new__(EventHook, topic, self.logger)
|
|
213
|
+
c_bytemap_set(hook_map, topic_ptr.key, topic_ptr.key_len, <void*> <PyObject*> event_hook, NULL)
|
|
214
|
+
Py_INCREF(event_hook)
|
|
215
|
+
elif not hook_ptr:
|
|
216
|
+
raise BufferError(f'Corrected buffer, invalid registered event hook.')
|
|
217
|
+
else:
|
|
218
|
+
event_hook = <EventHook> hook_ptr
|
|
219
|
+
event_hook.add_handler(py_callable, None, deduplicate)
|
|
220
|
+
|
|
221
|
+
cdef inline void c_unregister_handler(self, Topic topic, object py_callable):
|
|
222
|
+
cdef evt_topic* topic_ptr = topic.header
|
|
223
|
+
cdef PyObject* hook_ptr = NULL
|
|
224
|
+
cdef EventHook event_hook
|
|
225
|
+
cdef bytemap* hook_map
|
|
226
|
+
|
|
227
|
+
if topic_ptr.is_exact:
|
|
228
|
+
hook_map = self.exact_topic_hooks
|
|
229
|
+
else:
|
|
230
|
+
hook_map = self.generic_topic_hooks
|
|
231
|
+
|
|
232
|
+
cdef int ret_code = c_bytemap_get(hook_map, topic_ptr.key, topic_ptr.key_len, <void**> &hook_ptr)
|
|
233
|
+
if ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
|
|
234
|
+
LOGGER.error(f'No EventHook registered for "{topic.value}"')
|
|
235
|
+
return
|
|
236
|
+
event_hook = <EventHook> hook_ptr
|
|
237
|
+
event_hook.remove_handler(py_callable)
|
|
238
|
+
if len(event_hook) == 0:
|
|
239
|
+
c_bytemap_pop(hook_map, topic_ptr.key, topic_ptr.key_len, NULL)
|
|
240
|
+
Py_XDECREF(<PyObject*> event_hook)
|
|
241
|
+
|
|
242
|
+
cdef inline void c_clear(self):
|
|
243
|
+
cdef bytemap_entry* entry
|
|
244
|
+
cdef EventHook event_hook
|
|
245
|
+
|
|
246
|
+
# Clear exact_topic_hooks
|
|
247
|
+
entry = self.exact_topic_hooks.first
|
|
248
|
+
while entry:
|
|
249
|
+
if c_bytemap_entry_value(entry):
|
|
250
|
+
event_hook = <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
251
|
+
event_hook.clear()
|
|
252
|
+
Py_XDECREF(<PyObject*> event_hook)
|
|
253
|
+
entry = entry.next
|
|
254
|
+
c_bytemap_clear(self.exact_topic_hooks)
|
|
255
|
+
|
|
256
|
+
# Clear generic_topic_hooks
|
|
257
|
+
entry = self.generic_topic_hooks.first
|
|
258
|
+
while entry:
|
|
259
|
+
if c_bytemap_entry_value(entry):
|
|
260
|
+
event_hook = <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
261
|
+
event_hook.clear()
|
|
262
|
+
Py_XDECREF(<PyObject*> event_hook)
|
|
263
|
+
entry = entry.next
|
|
264
|
+
c_bytemap_clear(self.generic_topic_hooks)
|
|
265
|
+
|
|
266
|
+
# --- Python Interfaces---
|
|
267
|
+
|
|
268
|
+
def __len__(self):
|
|
269
|
+
count = 0
|
|
270
|
+
entry = self.exact_topic_hooks.first
|
|
271
|
+
while entry:
|
|
272
|
+
if c_bytemap_entry_value(entry):
|
|
273
|
+
count += 1
|
|
274
|
+
entry = entry.next
|
|
275
|
+
entry = self.generic_topic_hooks.first
|
|
276
|
+
while entry:
|
|
277
|
+
if c_bytemap_entry_value(entry):
|
|
278
|
+
count += 1
|
|
279
|
+
entry = entry.next
|
|
280
|
+
return count
|
|
281
|
+
|
|
282
|
+
def __repr__(self):
|
|
283
|
+
return f'<{self.__class__.__name__} {"active" if self.active else "idle"}>(capacity={self.capacity})'
|
|
284
|
+
|
|
285
|
+
def __getitem__(self, Topic topic) -> list[EventHook]:
|
|
286
|
+
cdef list out = []
|
|
287
|
+
cdef evt_topic* topic_ptr = topic.header
|
|
288
|
+
cdef PyObject* hook_ptr = NULL
|
|
289
|
+
cdef EventHook event_hook
|
|
290
|
+
|
|
291
|
+
cdef int ret_code = c_bytemap_get(self.exact_topic_hooks, topic.key, topic_ptr.key_len, <void**> &hook_ptr)
|
|
292
|
+
if hook_ptr:
|
|
293
|
+
event_hook = <EventHook> hook_ptr
|
|
294
|
+
Py_XINCREF(hook_ptr)
|
|
295
|
+
out.append(event_hook)
|
|
296
|
+
|
|
297
|
+
cdef bytemap_entry* entry = self.generic_topic_hooks.first
|
|
298
|
+
cdef int is_matched
|
|
299
|
+
while entry:
|
|
300
|
+
hook_ptr = <PyObject*> c_bytemap_entry_value(entry)
|
|
301
|
+
if not hook_ptr:
|
|
302
|
+
entry = entry.next
|
|
303
|
+
continue
|
|
304
|
+
event_hook = <EventHook> hook_ptr
|
|
305
|
+
is_matched = c_topic_match_bool(event_hook.topic.header, topic_ptr)
|
|
306
|
+
if is_matched:
|
|
307
|
+
Py_XINCREF(hook_ptr)
|
|
308
|
+
out.append(event_hook)
|
|
309
|
+
entry = entry.next
|
|
310
|
+
return out
|
|
311
|
+
|
|
312
|
+
def activate(self):
|
|
313
|
+
self.active = True
|
|
314
|
+
|
|
315
|
+
def deactivate(self):
|
|
316
|
+
self.active = False
|
|
317
|
+
|
|
318
|
+
def run(self):
|
|
319
|
+
self.c_loop()
|
|
320
|
+
|
|
321
|
+
def start(self):
|
|
322
|
+
if self.active:
|
|
323
|
+
self.logger.warning(f'{self} already started!')
|
|
324
|
+
return
|
|
325
|
+
self.active = True
|
|
326
|
+
self.engine = Thread(target=self.run, name='EventEngine')
|
|
327
|
+
self.engine.start()
|
|
328
|
+
self.logger.info(f'{self} started.')
|
|
329
|
+
|
|
330
|
+
def stop(self) -> None:
|
|
331
|
+
if not self.active:
|
|
332
|
+
self.logger.warning('EventEngine already stopped!')
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
self.active = False
|
|
336
|
+
self.engine.join()
|
|
337
|
+
|
|
338
|
+
def clear(self) -> None:
|
|
339
|
+
if self.active:
|
|
340
|
+
self.logger.error('EventEngine must be stopped before cleared!')
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
self.c_clear()
|
|
344
|
+
|
|
345
|
+
def get(self, bint block=True, size_t max_spin=DEFAULT_MQ_SPIN_LIMIT, double timeout=0.0) -> MessagePayload:
|
|
346
|
+
cdef evt_message_payload* msg = self.c_get(block, max_spin, timeout)
|
|
347
|
+
if not msg:
|
|
348
|
+
raise Empty()
|
|
349
|
+
cdef MessagePayload payload = MessagePayload.c_from_header(msg, True)
|
|
350
|
+
return payload
|
|
351
|
+
|
|
352
|
+
def put(self, Topic topic, *args, bint block=True, size_t max_spin=DEFAULT_MQ_SPIN_LIMIT, double timeout=0.0, **kwargs):
|
|
353
|
+
cdef int ret_code = self.c_publish(topic, args, kwargs, block, max_spin, timeout)
|
|
354
|
+
if ret_code:
|
|
355
|
+
raise Full()
|
|
356
|
+
|
|
357
|
+
def publish(self, Topic topic, tuple args, dict kwargs, bint block=True, size_t max_spin=DEFAULT_MQ_SPIN_LIMIT, double timeout=0.0):
|
|
358
|
+
cdef int ret_code = self.c_publish(topic, args, kwargs, block, max_spin, timeout)
|
|
359
|
+
if ret_code:
|
|
360
|
+
raise Full()
|
|
361
|
+
|
|
362
|
+
def get_hook(self, Topic topic):
|
|
363
|
+
return self.c_get_hook(topic)
|
|
364
|
+
|
|
365
|
+
def register_hook(self, EventHook hook):
|
|
366
|
+
self.c_register_hook(hook)
|
|
367
|
+
|
|
368
|
+
def unregister_hook(self, Topic topic) -> EventHook:
|
|
369
|
+
return self.c_unregister_hook(topic)
|
|
370
|
+
|
|
371
|
+
def register_handler(self, Topic topic, object handler, bint deduplicate=False):
|
|
372
|
+
self.c_register_handler(topic, handler, deduplicate)
|
|
373
|
+
|
|
374
|
+
def unregister_handler(self, Topic topic, object handler):
|
|
375
|
+
self.c_unregister_handler(topic, handler)
|
|
376
|
+
|
|
377
|
+
def event_hooks(self):
|
|
378
|
+
entry = self.exact_topic_hooks.first
|
|
379
|
+
while entry:
|
|
380
|
+
if c_bytemap_entry_value(entry):
|
|
381
|
+
yield <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
382
|
+
entry = entry.next
|
|
383
|
+
entry = self.generic_topic_hooks.first
|
|
384
|
+
while entry:
|
|
385
|
+
if c_bytemap_entry_value(entry):
|
|
386
|
+
yield <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
387
|
+
entry = entry.next
|
|
388
|
+
|
|
389
|
+
def topics(self):
|
|
390
|
+
entry = self.exact_topic_hooks.first
|
|
391
|
+
while entry:
|
|
392
|
+
if c_bytemap_entry_value(entry):
|
|
393
|
+
yield (<EventHook> <PyObject*> c_bytemap_entry_value(entry)).topic
|
|
394
|
+
entry = entry.next
|
|
395
|
+
entry = self.generic_topic_hooks.first
|
|
396
|
+
while entry:
|
|
397
|
+
if c_bytemap_entry_value(entry):
|
|
398
|
+
yield (<EventHook> <PyObject*> c_bytemap_entry_value(entry)).topic
|
|
399
|
+
entry = entry.next
|
|
400
|
+
|
|
401
|
+
def items(self):
|
|
402
|
+
entry = self.exact_topic_hooks.first
|
|
403
|
+
while entry:
|
|
404
|
+
if c_bytemap_entry_value(entry):
|
|
405
|
+
hook = <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
406
|
+
yield (hook.topic, hook)
|
|
407
|
+
entry = entry.next
|
|
408
|
+
entry = self.generic_topic_hooks.first
|
|
409
|
+
while entry:
|
|
410
|
+
if c_bytemap_entry_value(entry):
|
|
411
|
+
hook = <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
412
|
+
yield (hook.topic, hook)
|
|
413
|
+
entry = entry.next
|
|
414
|
+
|
|
415
|
+
property capacity:
|
|
416
|
+
def __get__(self):
|
|
417
|
+
return self.mq.capacity
|
|
418
|
+
|
|
419
|
+
property occupied:
|
|
420
|
+
def __get__(self):
|
|
421
|
+
return c_mq_occupied(self.mq)
|
|
422
|
+
|
|
423
|
+
property exact_topic_hook_map:
|
|
424
|
+
def __get__(self):
|
|
425
|
+
cdef dict out = {}
|
|
426
|
+
cdef bytemap_entry* entry = self.exact_topic_hooks.first
|
|
427
|
+
cdef str topic_str
|
|
428
|
+
cdef EventHook event_hook
|
|
429
|
+
while entry:
|
|
430
|
+
topic_str = PyUnicode_FromStringAndSize(entry.key, entry.key_length)
|
|
431
|
+
event_hook = <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
432
|
+
entry = entry.next
|
|
433
|
+
Py_XINCREF(<PyObject*> event_hook)
|
|
434
|
+
out[Topic(topic_str)] = event_hook
|
|
435
|
+
return out
|
|
436
|
+
|
|
437
|
+
property generic_topic_hook_map:
|
|
438
|
+
def __get__(self):
|
|
439
|
+
cdef dict out = {}
|
|
440
|
+
cdef bytemap_entry* entry = self.generic_topic_hooks.first
|
|
441
|
+
cdef str topic_str
|
|
442
|
+
cdef EventHook event_hook
|
|
443
|
+
while entry:
|
|
444
|
+
topic_str = PyUnicode_FromStringAndSize(entry.key, entry.key_length)
|
|
445
|
+
event_hook = <EventHook> <PyObject*> c_bytemap_entry_value(entry)
|
|
446
|
+
entry = entry.next
|
|
447
|
+
Py_XINCREF(<PyObject*> event_hook)
|
|
448
|
+
out[Topic(topic_str)] = event_hook
|
|
449
|
+
return out
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
cdef class EventEngineEx(EventEngine):
|
|
453
|
+
def __cinit__(self, size_t capacity=DEFAULT_MQ_CAPACITY, object logger=None):
|
|
454
|
+
self.timer = {}
|
|
455
|
+
|
|
456
|
+
cdef inline void c_timer_loop(self, double interval, Topic topic, datetime activate_time):
|
|
457
|
+
from time import sleep
|
|
458
|
+
cdef datetime scheduled_time
|
|
459
|
+
|
|
460
|
+
if activate_time is None:
|
|
461
|
+
scheduled_time = datetime.now()
|
|
462
|
+
else:
|
|
463
|
+
scheduled_time = activate_time
|
|
464
|
+
|
|
465
|
+
cdef dict kwargs = {'interval': interval, 'trigger_time': scheduled_time}
|
|
466
|
+
|
|
467
|
+
while self.active:
|
|
468
|
+
sleep_time = (scheduled_time - datetime.now()).total_seconds()
|
|
469
|
+
|
|
470
|
+
if sleep_time > 0:
|
|
471
|
+
sleep(sleep_time)
|
|
472
|
+
self.c_publish(topic, EMPTY_ARGS, kwargs, True, DEFAULT_MQ_SPIN_LIMIT, 0.0)
|
|
473
|
+
|
|
474
|
+
while scheduled_time < datetime.now():
|
|
475
|
+
scheduled_time += timedelta(seconds=interval)
|
|
476
|
+
kwargs['trigger_time'] = scheduled_time
|
|
477
|
+
|
|
478
|
+
cdef inline void c_minute_timer_loop(self, Topic topic):
|
|
479
|
+
from time import time, sleep
|
|
480
|
+
cdef double t, scheduled_time, next_time, sleep_time
|
|
481
|
+
cdef dict kwargs = {'interval': 60}
|
|
482
|
+
|
|
483
|
+
while self.active:
|
|
484
|
+
t = time()
|
|
485
|
+
scheduled_time = t // 60 * 60
|
|
486
|
+
next_time = scheduled_time + 60
|
|
487
|
+
sleep_time = next_time - t
|
|
488
|
+
sleep(sleep_time)
|
|
489
|
+
kwargs['timestamp'] = scheduled_time
|
|
490
|
+
self.c_publish(topic, EMPTY_ARGS, kwargs, True, DEFAULT_MQ_SPIN_LIMIT, 0.0)
|
|
491
|
+
|
|
492
|
+
cdef inline void c_second_timer_loop(self, Topic topic):
|
|
493
|
+
from time import time, sleep
|
|
494
|
+
cdef double t, scheduled_time, next_time, sleep_time
|
|
495
|
+
cdef dict kwargs = {'interval': 1}
|
|
496
|
+
|
|
497
|
+
while self.active:
|
|
498
|
+
t = time()
|
|
499
|
+
scheduled_time = t // 1
|
|
500
|
+
next_time = scheduled_time + 1
|
|
501
|
+
sleep_time = next_time - t
|
|
502
|
+
sleep(sleep_time)
|
|
503
|
+
kwargs['timestamp'] = scheduled_time
|
|
504
|
+
self.c_publish(topic, EMPTY_ARGS, kwargs, True, DEFAULT_MQ_SPIN_LIMIT, 0.0)
|
|
505
|
+
|
|
506
|
+
# --- Python Interfaces ---
|
|
507
|
+
|
|
508
|
+
def __repr__(self):
|
|
509
|
+
return f'<{self.__class__.__name__} {"active" if self.active else "idle"}>(capacity={self.capacity}, timers={list(self.timer.keys())})'
|
|
510
|
+
|
|
511
|
+
def run_timer(self, double interval, Topic topic, datetime activate_time=None):
|
|
512
|
+
if not self.active:
|
|
513
|
+
raise RuntimeError('EventEngine must be started before getting timer!')
|
|
514
|
+
self.c_timer_loop(interval, topic, activate_time)
|
|
515
|
+
|
|
516
|
+
def minute_timer(self, Topic topic):
|
|
517
|
+
if not self.active:
|
|
518
|
+
raise RuntimeError('EventEngine must be started before getting timer!')
|
|
519
|
+
self.c_minute_timer_loop(topic)
|
|
520
|
+
|
|
521
|
+
def second_timer(self, Topic topic):
|
|
522
|
+
if not self.active:
|
|
523
|
+
raise RuntimeError('EventEngine must be started before getting timer!')
|
|
524
|
+
self.c_second_timer_loop(topic)
|
|
525
|
+
|
|
526
|
+
def get_timer(self, double interval, datetime activate_time=None) -> Topic:
|
|
527
|
+
if not self.active:
|
|
528
|
+
raise RuntimeError('EventEngine must be started before getting timer!')
|
|
529
|
+
|
|
530
|
+
if interval == 1:
|
|
531
|
+
topic = Topic('EventEngine.Internal.Timer.Second')
|
|
532
|
+
timer = Thread(target=self.second_timer, kwargs={'topic': topic})
|
|
533
|
+
elif interval == 60:
|
|
534
|
+
topic = Topic('EventEngine.Internal.Timer.Minute')
|
|
535
|
+
timer = Thread(target=self.minute_timer, kwargs={'topic': topic})
|
|
536
|
+
else:
|
|
537
|
+
topic = Topic.join(['EventEngine', 'Internal', 'Timer', str(interval)])
|
|
538
|
+
timer = Thread(target=self.run_timer, kwargs={'interval': interval, 'topic': topic, 'activate_time': activate_time})
|
|
539
|
+
|
|
540
|
+
if interval not in self.timer:
|
|
541
|
+
self.timer[interval] = timer
|
|
542
|
+
timer.start()
|
|
543
|
+
else:
|
|
544
|
+
if activate_time is not None:
|
|
545
|
+
self.logger.debug(f'Timer thread with interval [{timedelta(seconds=interval)}] already initialized! Argument [activate_time] takes no effect!')
|
|
546
|
+
|
|
547
|
+
return topic
|
|
548
|
+
|
|
549
|
+
def stop(self) -> None:
|
|
550
|
+
super().stop()
|
|
551
|
+
|
|
552
|
+
for timer in self.timer.values():
|
|
553
|
+
timer.join()
|
|
554
|
+
|
|
555
|
+
def clear(self) -> None:
|
|
556
|
+
super().clear()
|
|
557
|
+
|
|
558
|
+
for t in self.timer.values():
|
|
559
|
+
t.join(timeout=0)
|
|
560
|
+
self.timer.clear()
|