""" Base classes for common session-related state shared between clients and proxies """ from __future__ import annotations import abc import logging import weakref from typing import * import multidict from hippolyzer.lib.base.datatypes import UUID, Vector3 from hippolyzer.lib.base.message.circuit import ConnectionHolder, Circuit from hippolyzer.lib.base.message.message import Message from hippolyzer.lib.base.message.message_handler import MessageHandler from hippolyzer.lib.base.network.caps_client import CapsClient from hippolyzer.lib.base.network.transport import ADDR_TUPLE from hippolyzer.lib.base.objects import handle_to_global_pos from hippolyzer.lib.client.object_manager import ClientObjectManager, ClientWorldObjectManager class BaseClientRegion(ConnectionHolder, abc.ABC): """Represents a client's view of a remote region""" handle: Optional[int] # Actually a weakref session: Callable[[], BaseClientSession] objects: ClientObjectManager caps_client: CapsClient cap_urls: multidict.MultiDict[str] circuit_addr: ADDR_TUPLE circuit: Optional[Circuit] _name: Optional[str] def __init__(self): self._name = None self.circuit = None @abc.abstractmethod def update_caps(self, caps: Mapping[str, str]) -> None: pass @property def name(self): if self._name: return self._name return "Pending %r" % (self.circuit_addr,) @name.setter def name(self, val): self._name = val @property def global_pos(self) -> Vector3: if self.handle is None: raise ValueError("Can't determine global region position without handle") return handle_to_global_pos(self.handle) @property def is_alive(self): if not self.circuit: return False return self.circuit.is_alive def mark_dead(self): logging.info("Marking %r dead" % self) if self.circuit: self.circuit.is_alive = False self.objects.clear() def __repr__(self): return "<%s %s (%r)>" % (self.__class__.__name__, self.name, self.handle) class BaseClientSessionManager: pass class BaseClientSession(abc.ABC): """Represents a client's view of a remote session""" id: UUID agent_id: UUID secure_session_id: UUID active_group: UUID groups: Set[UUID] message_handler: MessageHandler[Message, str] regions: MutableSequence[BaseClientRegion] region_by_handle: Callable[[int], Optional[BaseClientRegion]] region_by_circuit_addr: Callable[[ADDR_TUPLE], Optional[BaseClientRegion]] objects: ClientWorldObjectManager login_data: Dict[str, Any] REGION_CLS = Type[BaseClientRegion] def __init__(self, id, secure_session_id, agent_id, circuit_code, session_manager: Optional[BaseClientSessionManager], login_data=None): self.login_data = login_data or {} self.pending = True self.id: UUID = id self.secure_session_id: UUID = secure_session_id self.agent_id: UUID = agent_id self.circuit_code = circuit_code self.global_caps = {} self.session_manager = session_manager self.active_group: UUID = UUID.ZERO self.groups: Set[UUID] = set() self.regions = [] self._main_region = None self.message_handler: MessageHandler[Message, str] = MessageHandler() super().__init__() @classmethod def from_login_data(cls, login_data, session_manager): sess = cls( id=UUID(login_data["session_id"]), secure_session_id=UUID(login_data["secure_session_id"]), agent_id=UUID(login_data["agent_id"]), circuit_code=int(login_data["circuit_code"]), session_manager=session_manager, login_data=login_data, ) appearance_service = login_data.get("agent_appearance_service") map_image_service = login_data.get("map-server-url") if appearance_service: sess.global_caps["AppearanceService"] = appearance_service if map_image_service: sess.global_caps["MapImageService"] = map_image_service # Login data also has details about the initial sim sess.register_region( circuit_addr=(login_data["sim_ip"], login_data["sim_port"]), handle=(login_data["region_x"] << 32) | login_data["region_y"], seed_url=login_data["seed_capability"], ) return sess def register_region(self, circuit_addr: Optional[ADDR_TUPLE] = None, seed_url: Optional[str] = None, handle: Optional[int] = None) -> BaseClientRegion: if not any((circuit_addr, seed_url)): raise ValueError("One of circuit_addr and seed_url must be defined!") for region in self.regions: if region.circuit_addr == circuit_addr: if seed_url and region.cap_urls.get("Seed") != seed_url: region.update_caps({"Seed": seed_url}) if handle: region.handle = handle return region if seed_url and region.cap_urls.get("Seed") == seed_url: return region if not circuit_addr: raise ValueError("Can't create region without circuit addr!") logging.info("Registering region for %r" % (circuit_addr,)) region = self.REGION_CLS(circuit_addr, seed_url, self, handle=handle) self.regions.append(region) return region @property def main_region(self) -> Optional[BaseClientRegion]: if self._main_region and self._main_region() in self.regions: return self._main_region() return None @main_region.setter def main_region(self, val: BaseClientRegion): self._main_region = weakref.ref(val) def transaction_to_assetid(self, transaction_id: UUID): return UUID.combine(transaction_id, self.secure_session_id) def region_by_circuit_addr(self, circuit_addr) -> Optional[BaseClientRegion]: for region in self.regions: if region.circuit_addr == circuit_addr and region.circuit: return region return None def region_by_handle(self, handle: int) -> Optional[BaseClientRegion]: for region in self.regions: if region.handle == handle: return region return None def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.id)