8000 Return to os.captureEvent to prevent event losses · neumond/python-computer-craft@9305372 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9305372

Browse files
committed
Return to os.captureEvent to prevent event losses
1 parent c070543 commit 9305372

File tree

7 files changed

+97
-27
lines changed

7 files changed

+97
-27
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,14 @@ with fs.open('filename', 'r') as f:
5959
print(line)
6060
```
6161
62-
Waiting for event:
62+
Waiting for event (`os.captureEvent` instead `os.pullEvent`):
6363
6464
```python
6565
from cc import os
6666
6767
timer_id = os.startTimer(2)
68-
while True:
69-
e = os.pullEvent('timer')
70-
if e[1] == timer_id:
68+
for e in os.captureEvent('timer'):
69+
if e[0] == timer_id:
7170
print('Timer reached')
7271
break
7372
```

computercraft/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async def ws(self, request):
4949
if sess is not None:
5050
async for msg in self._json_messages(ws):
5151
if msg['action'] == 'event':
52-
pass
52+
sess.on_event(msg['event'], msg['params'])
5353
elif msg['action'] == 'task_result':
5454
sess.on_task_result(msg['task_id'], msg['result'])
5555
else:

computercraft/sess.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import string
33
import sys
44
from code import InteractiveConsole
5+
from collections import deque
56
from contextlib import contextmanager
67
from functools import partial
78
from importlib import import_module
@@ -138,8 +139,7 @@ def eval_lua(lua_code, immediate=False):
138139
'code': lua_code,
139140
'immediate': immediate,
140141
})
141-
# do not uncomment this, or use sys.__stdout__.write
142-
# print('{} → {}'.format(lua_code, repr(result)))
142+
# debug('{} → {}'.format(lua_code, repr(result)))
143143
if not immediate:
144144
result = rproc.coro(result)
145145
return result
@@ -237,6 +237,56 @@ def switch(self, *args, **kwargs):
237237
self._on_death()
238238

239239

240+
class CCEventRouter:
241+
def __init__(self, on_first_sub, on_last_unsub, resume_task):
242+
self._stacks = {}
243+
self._active = {}
244+
self._on_first_sub = on_first_sub
245+
self._on_last_unsub = on_last_unsub
246+
self._resume_task = resume_task
247+
248+
def sub(self, task_id, event):
249+
if event not in self._stacks:
250+
self._stacks[event] = {}
251+
self._on_first_sub(event)
252+
se = self._stacks[event]
253+
if task_id in se:
254+
raise Exception('Same task subscribes to the same event twice')
255+
se[task_id] = deque()
256+
257+
def unsub(self, task_id, event):
258+
if event not in self._stacks:
259+
return
260+
self._stacks[event].pop(task_id, None)
261+
if len(self._stacks[event]) == 0:
262+
self._on_last_unsub(event)
263+
del self._stacks[event]
264+
265+
def on_event(self, event, params):
266+
if event not in self._stacks:
267+
self._on_last_unsub(event)
268+
return
269+
for task_id, queue in self._stacks[event].items():
270+
queue.append(params)
271+
if self._active.get(task_id) == event:
272+
self._set_task_status(task_id, event, False)
273+
self._resume_task(task_id)
274+
275+
def get_from_stack(self, task_id, event):
276+
queue = self._stacks[event][task_id]
277+
try:
278+
return queue.popleft()
279+
except IndexError:
280+
self._set_task_status(task_id, event, True)
281+
return None
282+
283+
def _set_task_status(self, task_id, event, waits: bool):
284+
if waits:
285+
self._active[task_id] = event
286+
else:
287+
self._active.pop(task_id, None)
288+
289+
240290
class CCSession:
241291
def __init__(self, computer_id, sender):
242292
# computer_id is unique identifier of a CCSession
@@ -246,6 +296,11 @@ def __init__(self, computer_id, sender):
246296
self._greenlets = {}
247297
self._server_greenlet = get_current_greenlet()
248298
self._program_greenlet = None
299+
self._evr = CCEventRouter(
300+
lambda event: self._sender({'action': 'sub', 'event': event}),
301+
lambda event: self._sender({'action': 'unsub', 'event': event}),
302+
lambda task_id: self._greenlets[task_id].defer_switch('event'),
303+
)
249304

250305
def on_task_result(self, task_id, result):
251306
assert get_current_greenlet() is self._server_greenlet
@@ -254,6 +309,9 @@ def on_task_result(self, task_id, result):
254309
return
255310
self._greenlets[task_id].switch(result)
256311

312+
def on_event(self, event, params):
313+
self._evr.on_event(event, params)
314+
257315
def create_task_id(self):
258316
return next(self._tid_allocator)
259317

computercraft/subapis/os.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from typing import Optional, List
22

33
from ..lua import LuaNum
4-
from ..rproc import nil, string, option_string, number, integer, boolean, any_list
5-
from ..sess import eval_lua_method_factory
4+
from ..rproc import nil, string, option_string, number, integer, boolean
5+
from ..sess import eval_lua_method_factory, get_current_greenlet
66

77

88
method = eval_lua_method_factory('os.')
@@ -14,8 +14,7 @@
1414
'getComputerLabel',
1515
'setComputerLabel',
1616
'run',
17-
'pullEvent',
18-
'pullEventRaw',
17+
'captureEvent',
1918
'queueEvent',
2019
'clock',
2120
'time',
@@ -51,12 +50,21 @@ def run(environment: dict, programPath: str, *args: List[str]):
5150
return boolean(method('run', environment, programPath, *args))
5251

5352

54-
def pullEvent(event: str = None) -> tuple:
55-
return tuple(any_list(method('pullEvent', event)))
56-
57-
58-
def pullEventRaw(event: str = None) -> tuple:
59-
return tuple(any_list(method('pullEventRaw', event)))
53+
def captureEvent(event: str):
54+
glet = get_current_greenlet().cc_greenlet
55+
sess = glet._sess
56+
evr = sess._evr
57+
evr.sub(glet._task_id, event)
58+
try:
59+
while True:
60+
val = evr.get_from_stack(glet._task_id, event)
61+
if val is None:
62+
res = sess._server_greenlet.switch()
63+
assert res == 'event'
64+
else:
65+
yield val
66+
finally:
67+
evr.unsub(glet._task_id, event)
6068

6169

6270
def queueEvent(event: str, *params):

computercraft/subapis/peripheral.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,19 @@ def _side(self):
128128
return self._prepend_params[0]
129129

130130
def receive(self, channel: int):
131-
from .os import pullEvent
131+
from .os import captureEvent
132132

133133
if self.isOpen(channel):
134134
raise Exception('Channel is busy')
135135

136136
self.open(channel)
137137
try:
138-
while True:
139-
evt = pullEvent('modem_message')
140-
assert evt[0] == 'modem_message'
141-
if evt[1] != self._side:
138+
for evt in captureEvent('modem_message'):
139+
if evt[0] != self._side:
142140
continue
143-
if evt[2] != channel:
141+
if evt[1] != channel:
144142
continue
145-
yield ModemMessage(*evt[3:])
143+
yield ModemMessage(*evt[2:])
146144
finally:
147145
self.close(channel)
148146

examples/test_os.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@
2121

2222
with _lib.assert_takes_time(1.5, 3):
2323
timer_id = os.startTimer(2)
24-
while True:
25-
e = os.pullEvent('timer')
26-
if e[1] == timer_id:
24+
for e in os.captureEvent('timer'):
25+
if e[0] == timer_id:
2726
print('Timer reached')
2827
break
2928

examples/timer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from cc import os
2+
3+
4+
timer_id = os.startTimer(2)
5+
for e in os.captureEvent('timer'):
6+
if e[0] == timer_id:
7+
print('Timer reached')
8+
break

0 commit comments

Comments
 (0)
0