Spaces:
Paused
Paused
File size: 4,789 Bytes
0b48653 bf5738f 0b48653 bf5738f 0b48653 dda0437 0b48653 | 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 | """One isolated Unreal process. No connection to the private evaluation services."""
import ctypes, hashlib, json, os, signal, subprocess, time
from pathlib import Path
ROOT = Path(__file__).resolve().parent
class UnrealRuntime:
def __init__(self):
self.process = None
self.log = None
self.config = None
self.error = 'The interactive environment is being prepared. Please check back soon.'
path = os.environ.get('WAB_RUNTIME_CONFIG', str(ROOT / 'runtime.json'))
if not Path(path).is_file():
print('Runtime unavailable: set WAB_RUNTIME_CONFIG after restoring the Unreal Linux package.', flush=True)
return
try:
self.config = json.loads(Path(path).read_text())
binary = Path(self.config['binary'])
if not binary.is_file() or not os.access(binary, os.X_OK):
raise ValueError('Unreal binary is missing or not executable')
expected = self.config['sha256']
if len(expected) != 64 or hashlib.file_digest(binary.open('rb'),'sha256').hexdigest() != expected:
raise ValueError('Unreal binary hash does not match the configured release')
gpu=subprocess.run(['nvidia-smi','--query-gpu=name,driver_version','--format=csv,noheader'], check=True, capture_output=True, text=True, timeout=10)
ctypes.CDLL('libvulkan.so.1')
ctypes.CDLL('libnvidia-encode.so.1')
subprocess.run(['vulkaninfo', '--summary'], check=True, capture_output=True, timeout=20)
print('Runtime GPU ready: '+gpu.stdout.strip(),flush=True)
self.error = None
except Exception as exc:
print(f'Runtime preflight failed: {exc}', flush=True)
self.error = 'The interactive environment is temporarily unavailable.'
def start(self, case, session_id):
if self.error: raise RuntimeError(self.error)
self.stop()
folder = Path('/tmp/worldauditbench-sessions') / session_id
folder.mkdir(parents=True, exist_ok=True)
binary = Path(self.config['binary'])
args = [str(binary), case['map'], '-RenderOffscreen', '-vulkan', '-sm5',
'-AuditorSkipSceneMenu', '-AuditorRemoteInput',
'-DefaultViewportMouseCaptureMode=NoCapture',
'-ini:Input:[/Script/Engine.InputSettings]:bCaptureMouseOnLaunch=False',
'-ini:Input:[/Script/Engine.InputSettings]:DefaultViewportMouseLockMode=DoNotLock',
'-ForceRes', '-ResX=1920', '-ResY=1080', '-Unattended', '-AudioMixer',
'-PixelStreamingConnectionURL=ws://127.0.0.1:8888', '-PixelStreamingID=DefaultStreamer',
'-PixelStreamingEncoderCodec=H264', '-PixelStreamingWebRTCFps=30',
'-PixelStreamingWebRTCMaxBitrate=12000000', '-PixelStreamingWebRTCStartBitrate=5000000',
'-PixelStreamingAllowPixelStreamingCommands=false',
'-PixelStreamingKeyFilter="M,One,Two,Three,Four,Tilde"',
'-ExecCmds=t.MaxFPS 30,set HUD bShowHUD false', '-SaveToUserDir', f'-UserDir={folder}/user/',
f'-AuditorReviewIPC={folder}/ipc', '-log', f'-abslog={folder}/unreal.log']
policy = self.config.get('policy')
if policy:
args += [f'-AuditorExplorationPolicy={policy}', f'-AuditorExplorationTask={case["id"]}']
args += self.config.get('extra_args', [])
self.log = (folder/'stdout.log').open('ab')
# libwebsockets allocates an entry per allowed fd; Docker may advertise billions.
self.process = subprocess.Popen(['/bin/sh', '-c', 'ulimit -n 4096; exec "$@"', 'unreal', *args], cwd=binary.parent, stdout=self.log,
stderr=subprocess.STDOUT, start_new_session=True)
def alive(self):
return self.process is not None and self.process.poll() is None
def ready(self, case, session_id):
try:
ack=json.loads((Path('/tmp/worldauditbench-sessions')/session_id/'ipc/response.json').read_text())
return (self.alive() and ack.get('status')=='ready' and
ack.get('pid')==self.process.pid and ack.get('task')==case['id'] and
ack.get('map')==case['map'].partition('?')[0])
except (OSError,ValueError):return False
def stop(self):
if self.process is not None:
if self.process.poll() is None:
os.killpg(self.process.pid, signal.SIGTERM)
try: self.process.wait(timeout=8)
except subprocess.TimeoutExpired:
os.killpg(self.process.pid, signal.SIGKILL)
self.process.wait(timeout=5)
self.process = None
if self.log: self.log.close(); self.log=None
|