diff --git a/pymetaverse/viewer/agent.py b/pymetaverse/viewer/agent.py index a5b2372..e9f8049 100644 --- a/pymetaverse/viewer/agent.py +++ b/pymetaverse/viewer/agent.py @@ -73,11 +73,26 @@ class Agent(EventTarget): self.send(msg, True) async def run(self): + lastAck = 0 while True: try: await asyncio.sleep(0.1) if self.simulator == None: break + + if not "EventQueueGet" in self.simulator.capabilities: + lastAck = 0 + continue + + ack, events = await self.simulator.capabilities["EventQueueGet"].poll(lastAck, False) + if ack == None: + lastAck = 0 + continue + + if ack == lastAck: + continue + + lastAck = ack except asyncio.exceptions.CancelledError: # Attempt to gracefully logout diff --git a/pymetaverse/viewer/capability.py b/pymetaverse/viewer/capability.py new file mode 100644 index 0000000..18dae1d --- /dev/null +++ b/pymetaverse/viewer/capability.py @@ -0,0 +1,72 @@ +from .. import httpclient +from .. import llsd + +class CapabilityRegistry: + def __init__(self): + self.capabilities = {} + + def register(self, name): + def _(func): + self.capabilities[name] = func + return func + return _ + + def __contains__(self, what): + return what in self.capabilities + + def get(self, name, url): + try: + return self.capabilities[name](url) + except KeyError: + raise ValueError("No such capability {}".format(name)) + +Capabilities = CapabilityRegistry() + +class BaseCapability: + def __init__(self, url): + self.url = url + +# Please keep these in alphabetical order! :) + +@Capabilities.register("EventQueueGet") +class EventQueueGet(BaseCapability): + async def poll(self, ack, done = False): + async with httpclient.HttpClient() as session: + async with await session.post(self.url, + data = llsd.llsdEncode({ + "ack": ack, + "done": done + }), + headers = { + "Content-Type": "application/llsd+xml" + }, + timeout = 60 # Timeouts on the server are 30, twice that is more than enough + ) as response: + if response.status == 404: + return None, [] + + elif response.status == 200: + data = await response.read() + result = llsd.llsdDecode(data, format="xml") + return result["id"], result["events"] + + else: + return ack, [] + +@Capabilities.register("Seed") +class Seed(BaseCapability): + async def getCapabilities(self, caps): + async with httpclient.HttpClient() as session: + async with await session.post(self.url, + data = llsd.llsdEncode(list(caps.capabilities.keys())), + headers = { + "Content-Type": "application/llsd+xml" + } + ) as response: + capList = llsd.llsdDecode(await response.read(), format="xml") + result = {} + for name, url in capList.items(): + if name in caps: + result[name] = caps.get(name, url) + + return result diff --git a/pymetaverse/viewer/simulator.py b/pymetaverse/viewer/simulator.py index f16b9d9..8c693a3 100644 --- a/pymetaverse/viewer/simulator.py +++ b/pymetaverse/viewer/simulator.py @@ -1,5 +1,7 @@ +import asyncio from .circuit import Circuit from . import messages +from .capability import Capabilities from . import region from .. import httpclient from .. import llsd @@ -16,9 +18,16 @@ class Simulator(EventTarget): self.id = None self.circuit = None self.region = None - self.caps = {} + self.capabilities = {} self.messageTemplate = messages.getDefaultTemplate() + def __del__(self): + try: + loop = asyncio.get_running_loop() + loop.create_task(self.close()) + except RuntimeError: + pass + def send(self, msg, reliable = False): self.circuit.send(msg, reliable) @@ -74,8 +83,12 @@ class Simulator(EventTarget): except Exception as e: traceback.print_exc() - async def fetchCapabilities(self, seed): - pass + async def fetchCapabilities(self, url): + if "Seed" not in Capabilities: + return + + seed = Capabilities.get("Seed", url) + self.capabilities = await seed.getCapabilities(Capabilities) def __repr__(self): return f"<{self.__class__.__name__} \"{self.name}\" ({self.host[0]}:{self.host[1]})>"