Files
pymetaverse/metaverse/viewer/simulator.py
2025-08-19 09:04:58 -04:00

162 lines
5.3 KiB
Python

import asyncio
from .circuit import Circuit
from . import messages
from .capability import Capabilities
from . import region
from .. import httpclient
from .. import llsd
from . import eventqueue
from ..eventtarget import EventTarget
import time
import traceback
import logging
logger = logging.getLogger(__name__)
class Simulator(EventTarget):
def __init__(self, agent):
super().__init__()
self.agent = agent
self.host = None
self.name = "Unknown Region"
self.owner = None
self.id = None
self.circuit = None
self.region = None
self.lastMessage = time.time()
self.capabilities = {}
self.pingSequence = 0
self.pendingPings = {}
self.eventQueue = eventqueue.EventQueue(self)
self.eventQueue.on("event", self.handleEvent)
self.messageTemplate = messages.getDefaultTemplate()
def __del__(self):
try:
self.close()
except RuntimeError:
pass
def send(self, msg, reliable = False):
self.circuit.send(msg, reliable)
async def connect(self, host, circuitCode):
self.host = host
self.circuit = await Circuit.create(host)
self.circuit.on("message", self.handleMessage)
msg = self.messageTemplate.getMessage("UseCircuitCode")
msg.CircuitCode.Code = circuitCode
msg.CircuitCode.SessionID = self.agent.sessionId
msg.CircuitCode.ID = self.agent.agentId
self.send(msg, True)
async def handleSystemMessages(self, msg):
if msg.name == "PacketAck":
acks = []
for ack in msg.Packets:
acks.append(ack.ID)
self.circuit.acknowledge(acks)
elif msg.name == "StartPingCheck":
msg = self.messageTemplate.getMessage("CompletePingCheck")
msg.PingID.PingID = msg.PingID.PingID
self.send(msg)
elif msg.name == "CompletePingCheck":
if msg.PingID.PingID in self.pendingPings:
future = self.pendingPings[msg.PingID.PingID]
if not future.done():
future.set_result(False)
del self.pendingPings[msg.PingID.PingID]
elif msg.name == "RegionHandshake":
self.name = msg.RegionInfo.SimName.rstrip(b"\0").decode()
self.owner = msg.RegionInfo.SimOwner
self.id = msg.RegionInfo2.RegionID
logger.debug(f"Received handshake for {self}")
msg = self.messageTemplate.getMessage("RegionHandshakeReply")
msg.AgentData.AgentID = self.agent.agentId
msg.AgentData.SessionID = self.agent.sessionId
msg.RegionInfo.Flags = 1
self.send(msg, True)
elif msg.name == "DisableSimulator":
self.close()
async def handleMessage(self, addr, body):
# Reject unknown hosts as a security precaution
if addr != self.host:
return
self.lastMessage = time.time()
msg = self.messageTemplate.loadMessage(body)
await self.handleSystemMessages(msg)
# Don't break the whole script!
try:
await self.fire("message", self, msg, name=msg.name)
except Exception as e:
traceback.print_exc()
async def handleEvent(self, name, body):
try:
await self.fire("event", self, name, body)
except Exception as e:
traceback.print_exc()
async def fetchCapabilities(self, url = None):
if "Seed" not in Capabilities:
return
seed = Capabilities.get("Seed", url)
self.capabilities = await seed.getCapabilities(Capabilities)
self.eventQueue.start()
async def ping(self, timeout = 5.0, forceUsePingCheck = False):
# If we already have a message within the timeout range,
# just use that instead. Unless specified otherwise.
if not forceUsePingCheck and self.lastMessage + timeout > time.time():
return True
logger.debug(f"Starting ping check for {self}")
loop = asyncio.get_running_loop()
future = loop.create_future()
currentPing = self.pingSequence
self.pingSequence = (currentPing + 1) & 0xFF
# If it exists at this point, it's probably not ever going to
# come in
if currentPing in self.pendingPings:
old_future = self.pendingPings[currentPing]
if not old_future.done():
old_future.set_result(False)
del self.pendingPings[currentPing]
msg = self.messageTemplate.getMessage("StartPingCheck")
msg.PingID.PingID = currentPing
self.pendingPings[msg.PingID.PingID] = future
self.send(msg)
try:
await asyncio.wait_for(future, timeout=timeout)
except asyncio.TimeoutError:
if msg.PingID.PingID in self.pendingPings:
del self.pendingPings[msg.PingID.PingID]
return False
return True
def close(self):
self.eventQueue.close()
self.circuit.close()
def __repr__(self):
return f"<{self.__class__.__name__} \"{self.name}\" ({self.host[0]}:{self.host[1]})>"