feat(simulation): add Worker AI polygon runtime and terrain navigation
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Private loopback transport over the operator's dedicated SSH reverse tunnel."""
|
||||
|
||||
import hashlib
|
||||
import http.client
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
PREFIX = "/api/v1/ai-polygon"
|
||||
|
||||
|
||||
class CoreClient:
|
||||
def __init__(self, origin: str, token_file: Path, instance: str):
|
||||
url = urlsplit(origin)
|
||||
if (
|
||||
url.scheme != "http"
|
||||
or url.hostname != "127.0.0.1"
|
||||
or url.path
|
||||
or url.query
|
||||
or url.fragment
|
||||
or url.username
|
||||
or not url.port
|
||||
):
|
||||
raise ValueError("Core must use an explicit loopback SSH tunnel origin")
|
||||
self.port = url.port
|
||||
self.token = token_file.read_text().strip()
|
||||
if not 32 <= len(self.token) <= 512:
|
||||
raise ValueError("Invalid simulation credential")
|
||||
self.instance = instance
|
||||
self._connections = threading.local()
|
||||
|
||||
def request(self, path: str, body=None):
|
||||
connection = getattr(self._connections, "connection", None)
|
||||
if (
|
||||
connection is not None
|
||||
and time.monotonic() - getattr(self._connections, "last_used", 0) > 2
|
||||
):
|
||||
connection.close()
|
||||
connection = None
|
||||
if connection is None:
|
||||
connection = http.client.HTTPConnection("127.0.0.1", self.port, timeout=10)
|
||||
self._connections.connection = connection
|
||||
try:
|
||||
connection.request(
|
||||
"GET" if body is None else "POST",
|
||||
PREFIX + path,
|
||||
body=None if body is None else json.dumps(body, allow_nan=False).encode(),
|
||||
headers={
|
||||
"Authorization": "Bearer " + self.token,
|
||||
"Worker-Instance": self.instance,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
)
|
||||
response = connection.getresponse()
|
||||
raw = response.read(4 * 1024**2 + 1)
|
||||
if response.status not in (200, 201) or len(raw) > 4 * 1024**2:
|
||||
raise RuntimeError("Core rejected simulation operation: " + str(response.status))
|
||||
self._connections.last_used = time.monotonic()
|
||||
return json.loads(raw)
|
||||
except Exception:
|
||||
connection.close()
|
||||
self._connections.connection = None
|
||||
# Never retry an uncertain mutation automatically.
|
||||
raise
|
||||
|
||||
def download(self, world: dict, target: Path):
|
||||
if target.exists():
|
||||
with target.open("rb") as stream:
|
||||
if hashlib.file_digest(stream, "sha256").hexdigest() == world["sha256"]:
|
||||
return
|
||||
if world.get("storage", {}).get("kind") == "worker":
|
||||
raise RuntimeError("The admitted Worker-local scene is missing or changed")
|
||||
connection = http.client.HTTPConnection("127.0.0.1", self.port, timeout=30)
|
||||
temporary = target.with_suffix(".part")
|
||||
try:
|
||||
connection.request("GET", PREFIX + "/worlds/" + world["world_id"] + "/source.ply")
|
||||
response = connection.getresponse()
|
||||
if (
|
||||
response.status != 200
|
||||
or int(response.getheader("Content-Length") or 0) != world["byte_length"]
|
||||
):
|
||||
raise RuntimeError("Scene transfer contract changed")
|
||||
digest, size = hashlib.sha256(), 0
|
||||
with temporary.open("wb") as stream:
|
||||
while block := response.read(4 * 1024**2):
|
||||
size += len(block)
|
||||
if size > world["byte_length"]:
|
||||
raise RuntimeError("Scene transfer exceeds admitted size")
|
||||
digest.update(block)
|
||||
stream.write(block)
|
||||
if size != world["byte_length"] or digest.hexdigest() != world["sha256"]:
|
||||
raise RuntimeError("Scene transfer identity changed")
|
||||
temporary.replace(target)
|
||||
finally:
|
||||
connection.close()
|
||||
Reference in New Issue
Block a user