diff --git a/hippolyzer/lib/base/objects.py b/hippolyzer/lib/base/objects.py index a7f46a8..569c740 100644 --- a/hippolyzer/lib/base/objects.py +++ b/hippolyzer/lib/base/objects.py @@ -270,6 +270,9 @@ def normalize_object_update_compressed_data(data: bytes): # Only used for determining which sections are present del compressed["Flags"] + # Unlike other ObjectUpdate types, a null value in an ObjectUpdateCompressed + # always means that there is no value, not that the value hasn't changed + # from the client's view. Use the default value when that happens. ps_block = compressed.pop("PSBlockNew", None) if ps_block is None: ps_block = compressed.pop("PSBlock", None) @@ -278,6 +281,20 @@ def normalize_object_update_compressed_data(data: bytes): compressed.pop("PSBlock", None) if compressed["NameValue"] is None: compressed["NameValue"] = NameValueCollection() + if compressed["Text"] is None: + compressed["Text"] = b"" + compressed["TextColor"] = b"" + if compressed["MediaURL"] is None: + compressed["MediaURL"] = b"" + if compressed["AngularVelocity"] is None: + compressed["AngularVelocity"] = Vector3() + if compressed["SoundFlags"] is None: + compressed["SoundFlags"] = 0 + compressed["SoundGain"] = 0.0 + compressed["SoundRadius"] = 0.0 + compressed["Sound"] = UUID() + if compressed["TextureEntry"] is None: + compressed["TextureEntry"] = tmpls.TextureEntry() object_data = { "PSBlock": ps_block.value, @@ -286,9 +303,9 @@ def normalize_object_update_compressed_data(data: bytes): "LocalID": compressed.pop("ID"), **compressed, } - if object_data["TextureEntry"] is None: - object_data.pop("TextureEntry") - # Don't clobber OwnerID in case the object has a proper one. + # Don't clobber OwnerID in case the object has a proper one from + # a previous ObjectProperties. OwnerID isn't expected to be populated + # on ObjectUpdates unless an attached sound is playing. if object_data["OwnerID"] == UUID(): del object_data["OwnerID"] return object_data diff --git a/hippolyzer/lib/proxy/object_manager.py b/hippolyzer/lib/proxy/object_manager.py index 94396c0..0b9faa5 100644 --- a/hippolyzer/lib/proxy/object_manager.py +++ b/hippolyzer/lib/proxy/object_manager.py @@ -63,18 +63,21 @@ class ProxyObjectManager(ClientObjectManager): cache_dir=self._region.session().cache_dir, ) - def request_missed_cached_objects_soon(self): + def request_missed_cached_objects_soon(self, report_only=False): if self._cache_miss_timer: self._cache_miss_timer.cancel() # Basically debounce. Will only trigger 0.2 seconds after the last time it's invoked to # deal with the initial flood of ObjectUpdateCached and the natural lag time between that # and the viewers' RequestMultipleObjects messages - self._cache_miss_timer = asyncio.get_event_loop().call_later( - 0.2, self._request_missed_cached_objects) + loop = asyncio.get_event_loop_policy().get_event_loop() + self._cache_miss_timer = loop.call_later(0.2, self._request_missed_cached_objects, report_only) - def _request_missed_cached_objects(self): + def _request_missed_cached_objects(self, report_only: bool): self._cache_miss_timer = None - self.request_objects(self.queued_cache_misses) + if report_only: + print(f"Would have automatically requested {self.queued_cache_misses!r}") + else: + self.request_objects(self.queued_cache_misses) self.queued_cache_misses.clear() def clear(self): @@ -111,8 +114,11 @@ class ProxyWorldObjectManager(ClientWorldObjectManager): def _handle_object_update_cached_misses(self, region_handle: int, missing_locals: Set[int]): if not self._settings.ALLOW_AUTO_REQUEST_OBJECTS: - return - if self._settings.AUTOMATICALLY_REQUEST_MISSING_OBJECTS: + if self._settings.USE_VIEWER_OBJECT_CACHE: + region_mgr: Optional[ProxyObjectManager] = self._get_region_manager(region_handle) + region_mgr.queued_cache_misses |= missing_locals + region_mgr.request_missed_cached_objects_soon(report_only=True) + elif self._settings.AUTOMATICALLY_REQUEST_MISSING_OBJECTS: # Schedule these local IDs to be requested soon if the viewer doesn't request # them itself. Ideally we could just mutate the CRC of the ObjectUpdateCached # to force a CRC cache miss in the viewer, but that appears to cause the viewer diff --git a/tests/proxy/test_object_manager.py b/tests/proxy/test_object_manager.py index e777b20..53ff8ff 100644 --- a/tests/proxy/test_object_manager.py +++ b/tests/proxy/test_object_manager.py @@ -418,13 +418,13 @@ class RegionObjectManagerTests(ObjectManagerTestMixin, unittest.IsolatedAsyncioT 'AngularVelocity': Vector3(0.0, 0.0, 0.0791015625), 'TreeSpecies': None, 'ScratchPad': None, - 'Text': None, - 'TextColor': None, - 'MediaURL': None, - 'Sound': None, - 'SoundGain': None, - 'SoundFlags': None, - 'SoundRadius': None, + 'Text': b'', + 'TextColor': b'', + 'MediaURL': b'', + 'Sound': UUID(), + 'SoundGain': 0.0, + 'SoundFlags': 0, + 'SoundRadius': 0.0, 'NameValue': [], 'PathCurve': 32, 'ProfileCurve': 0,