File size: 3,863 Bytes
5dd27d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""Drive engine/host.mjs: the game runs in Node, the scene comes back as arrays."""
import json
import os
import struct
import subprocess
from pathlib import Path

import numpy as np

ROOT = Path(__file__).resolve().parent
EDEN = Path(os.environ.get('EDEN_DEPLOY_ROOT', '/root/autodl-tmp/edenlive'))
GAMES = Path(os.environ.get('EDEN_GAMES_ROOT', EDEN / 'EdenLive/realtime/games'))
NODE = EDEN / 'node/bin/node'

_DTYPES = {'Float32Array': np.float32, 'Uint32Array': np.uint32, 'Uint16Array': np.uint16, 'Int32Array': np.int32}


def standalone_config(entry):
    """The same window.gameStandalone payload the capture browser's page gets."""
    return {'restartKeys': list(entry.get('restart_keys') or ['r']),
            'eventPrompts': dict(entry.get('event_prompts') or {})}


class Engine:
    def __init__(self, slug='eiffel-assault', width=960, height=544, games_root=None, config=None):
        games = Path(games_root or GAMES)
        if config is None:
            entry = next(g for g in json.loads((games / 'registry.json').read_text())['games'] if g['slug'] == slug)
            config = standalone_config(entry)
        self.proc = subprocess.Popen(
            [str(NODE), str(ROOT / 'engine/host.mjs'), str(games / slug),
             str(games / 'node_modules/three/build/three.module.js'), str(width), str(height), json.dumps(config)],
            stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0)
        self.scene, self.scene_arrays = self._read()
        if self.scene.get('type') != 'scene':
            raise RuntimeError(f'Unexpected first message: {self.scene.get("type")}')

    def _read_exact(self, n):
        chunks, remaining = [], n
        while remaining:
            chunk = self.proc.stdout.read(remaining)
            if not chunk:
                raise EOFError('Engine exited')
            chunks.append(chunk)
            remaining -= len(chunk)
        return b''.join(chunks)

    def _read(self):
        (size,) = struct.unpack('<I', self._read_exact(4))
        header = json.loads(self._read_exact(size))
        (size,) = struct.unpack('<I', self._read_exact(4))
        binary = self._read_exact(size)
        arrays = {name: np.frombuffer(binary, _DTYPES[spec['dtype']], spec['length'], spec['offset'])
                  for name, spec in header.pop('arrays', {}).items()}
        return header, arrays

    def advance(self, target_tick, events=(), capture=False, state=True):
        self.send_advance(target_tick, events, capture, state)
        return self.receive()

    def send_advance(self, target_tick, events=(), capture=False, state=True):
        """Queue one step without waiting; replies come back in order through receive()."""
        command = {'cmd': 'advance', 'targetTick': int(target_tick), 'events': list(events),
                   'capture': bool(capture), 'state': bool(state)}
        self.proc.stdin.write((json.dumps(command) + '\n').encode())

    def receive(self):
        return self._read()

    def send_chunk(self, frames):
        """frames: [{'targetTick', 'events', 'capture'}]. Read back with receive():
        one ('scene', arrays) message per captured frame, then the closing 'chunk'."""
        command = {'cmd': 'chunk', 'frames': [dict(targetTick=int(f['targetTick']), events=list(f['events']),
                                                   capture=bool(f['capture'])) for f in frames]}
        self.proc.stdin.write((json.dumps(command) + '\n').encode())

    def probe(self, events=()):
        self.proc.stdin.write((json.dumps({'cmd': 'probe', 'events': list(events)}) + '\n').encode())
        return self._read()[0]

    def close(self):
        if self.proc.poll() is None:
            try:
                self.proc.stdin.write(b'{"cmd":"quit"}\n')
                self.proc.wait(5)
            except Exception:
                self.proc.kill()