styly-netsync-server 0.10.2__tar.gz → 0.10.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {styly_netsync_server-0.10.2/src/styly_netsync_server.egg-info → styly_netsync_server-0.10.4}/PKG-INFO +1 -1
  2. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/pyproject.toml +1 -1
  3. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/binary_serializer.py +235 -1
  4. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/client_simulator.py +10 -0
  5. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/server.py +363 -39
  6. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4/src/styly_netsync_server.egg-info}/PKG-INFO +1 -1
  7. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync_server.egg-info/SOURCES.txt +2 -0
  8. styly_netsync_server-0.10.4/tests/test_discovery_probe.py +103 -0
  9. styly_netsync_server-0.10.4/tests/test_object_sync.py +440 -0
  10. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/LICENSE +0 -0
  11. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/README.md +0 -0
  12. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/setup.cfg +0 -0
  13. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/__init__.py +0 -0
  14. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/__main__.py +0 -0
  15. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/adapters.py +0 -0
  16. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/cli.py +0 -0
  17. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/client.py +0 -0
  18. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/config.py +0 -0
  19. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/default.toml +0 -0
  20. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/events.py +0 -0
  21. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/logging_utils.py +0 -0
  22. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/network_utils.py +0 -0
  23. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/nv_sync.py +0 -0
  24. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/rest_bridge.py +0 -0
  25. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync/types.py +0 -0
  26. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync_server.egg-info/dependency_links.txt +0 -0
  27. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync_server.egg-info/entry_points.txt +0 -0
  28. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync_server.egg-info/requires.txt +0 -0
  29. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/src/styly_netsync_server.egg-info/top_level.txt +0 -0
  30. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_all_run_methods.py +0 -0
  31. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_binary_serializer.py +0 -0
  32. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_config.py +0 -0
  33. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_logging_cli.py +0 -0
  34. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_multi_nic.py +0 -0
  35. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_nv_protocol.py +0 -0
  36. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_port_error_message.py +0 -0
  37. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_python_client.py +0 -0
  38. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_reconnect_identity.py +0 -0
  39. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_rest_bridge.py +0 -0
  40. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_room_expiry.py +0 -0
  41. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_stealth_heartbeat.py +0 -0
  42. {styly_netsync_server-0.10.2 → styly_netsync_server-0.10.4}/tests/test_timing_monotonic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: styly-netsync-server
3
- Version: 0.10.2
3
+ Version: 0.10.4
4
4
  Summary: STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences
5
5
  Author-email: "STYLY, Inc." <info@styly.inc>
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "styly-netsync-server"
7
- version = "0.10.2"
7
+ version = "0.10.4"
8
8
  description = "STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -19,6 +19,11 @@ MSG_CLIENT_VAR_SET = 9 # Set client variable
19
19
  MSG_CLIENT_VAR_SYNC = 10 # Sync client variables
20
20
  MSG_CLIENT_POSE = 11
21
21
  MSG_ROOM_POSE = 12
22
+ MSG_OBJECT_POSE = 13 # Client → Server: owned object Transform
23
+ MSG_ROOM_OBJECTS = 14 # Server → Clients (PUB): room object states
24
+ MSG_OBJECT_OWNERSHIP_REQUEST = 15 # Client → Server: RequestOwnership/ReleaseOwnership
25
+ MSG_OBJECT_OWNERSHIP_CHANGED = 16 # Server → Clients (ROUTER): ownership changed
26
+ MSG_OBJECT_OWNERSHIP_REJECTED = 17 # Server → Client (ROUTER): request rejected
22
27
 
23
28
  # Transform data type identifiers (deprecated - kept for reference)
24
29
 
@@ -777,7 +782,10 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
777
782
  offset += 1
778
783
 
779
784
  # Validate message type is within valid range
780
- if message_type < MSG_CLIENT_TRANSFORM or message_type > MSG_ROOM_POSE:
785
+ if (
786
+ message_type < MSG_CLIENT_TRANSFORM
787
+ or message_type > MSG_OBJECT_OWNERSHIP_REJECTED
788
+ ):
781
789
  # Return invalid message type with None data instead of raising exception
782
790
  return message_type, None, b""
783
791
 
@@ -806,6 +814,28 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
806
814
  return message_type, _deserialize_client_var_set(data, offset), b""
807
815
  elif message_type == MSG_CLIENT_VAR_SYNC:
808
816
  return message_type, _deserialize_client_var_sync(data, offset), b""
817
+ elif message_type == MSG_OBJECT_POSE:
818
+ return message_type, _deserialize_object_pose(data, offset), b""
819
+ elif message_type == MSG_ROOM_OBJECTS:
820
+ return message_type, _deserialize_room_objects(data, offset), b""
821
+ elif message_type == MSG_OBJECT_OWNERSHIP_REQUEST:
822
+ return (
823
+ message_type,
824
+ _deserialize_object_ownership_request(data, offset),
825
+ b"",
826
+ )
827
+ elif message_type == MSG_OBJECT_OWNERSHIP_CHANGED:
828
+ return (
829
+ message_type,
830
+ _deserialize_object_ownership_changed(data, offset),
831
+ b"",
832
+ )
833
+ elif message_type == MSG_OBJECT_OWNERSHIP_REJECTED:
834
+ return (
835
+ message_type,
836
+ _deserialize_object_ownership_rejected(data, offset),
837
+ b"",
838
+ )
809
839
  else:
810
840
  # Should not reach here due to validation above
811
841
  return message_type, None, b""
@@ -1225,3 +1255,207 @@ def _deserialize_client_var_sync(data: bytes, offset: int) -> dict[str, Any]:
1225
1255
  result["clientVariables"][str(client_no)] = variables
1226
1256
 
1227
1257
  return result
1258
+
1259
+
1260
+ # ---------------------------------------------------------------------------
1261
+ # NetSyncObject serialization / deserialization
1262
+ # ---------------------------------------------------------------------------
1263
+
1264
+
1265
+ def serialize_object_pose(data: dict[str, Any]) -> bytes:
1266
+ """Serialize object pose message (Client -> Server)."""
1267
+ buffer = bytearray()
1268
+ buffer.append(MSG_OBJECT_POSE)
1269
+ buffer.append(PROTOCOL_VERSION)
1270
+ object_id = int(data.get("objectId", 0)) & 0xFFFFFFFF
1271
+ buffer.extend(struct.pack("<I", object_id))
1272
+ buffer.extend(struct.pack("<H", int(data.get("poseSeq", 0)) & 0xFFFF))
1273
+ # Position: int24 x3
1274
+ _pack_int24_le(
1275
+ buffer, _quantize_signed_int24(float(data.get("posX", 0.0)), ABS_POS_SCALE)
1276
+ )
1277
+ _pack_int24_le(
1278
+ buffer, _quantize_signed_int24(float(data.get("posY", 0.0)), ABS_POS_SCALE)
1279
+ )
1280
+ _pack_int24_le(
1281
+ buffer, _quantize_signed_int24(float(data.get("posZ", 0.0)), ABS_POS_SCALE)
1282
+ )
1283
+ # Rotation: smallest-three 32-bit
1284
+ qx = float(data.get("rotX", 0.0))
1285
+ qy = float(data.get("rotY", 0.0))
1286
+ qz = float(data.get("rotZ", 0.0))
1287
+ qw = float(data.get("rotW", 1.0))
1288
+ packed_rot = _compress_quaternion_smallest_three(qx, qy, qz, qw)
1289
+ buffer.extend(struct.pack("<I", packed_rot))
1290
+ return bytes(buffer)
1291
+
1292
+
1293
+ def serialize_room_objects(
1294
+ room_id: str, broadcast_time: float, objects: list[dict[str, Any]]
1295
+ ) -> bytes:
1296
+ """Serialize room objects broadcast message (Server -> Clients via PUB)."""
1297
+ buffer = bytearray()
1298
+ buffer.append(MSG_ROOM_OBJECTS)
1299
+ buffer.append(PROTOCOL_VERSION)
1300
+ buffer.extend(struct.pack("<d", broadcast_time))
1301
+ buffer.extend(struct.pack("<H", len(objects)))
1302
+ for obj in objects:
1303
+ object_id = int(obj.get("objectId", 0)) & 0xFFFFFFFF
1304
+ buffer.extend(struct.pack("<I", object_id))
1305
+ buffer.extend(struct.pack("<H", int(obj.get("ownerClientNo", 0)) & 0xFFFF))
1306
+ buffer.extend(struct.pack("<H", int(obj.get("poseSeq", 0)) & 0xFFFF))
1307
+ buffer.extend(struct.pack("<d", float(obj.get("poseTime", 0.0))))
1308
+ # body_bytes already contains pos(9B) + rot(4B) = 13 bytes
1309
+ body = obj.get("bodyBytes", b"")
1310
+ if body:
1311
+ buffer.extend(body)
1312
+ else:
1313
+ # Fallback: serialize from individual fields
1314
+ _pack_int24_le(
1315
+ buffer,
1316
+ _quantize_signed_int24(float(obj.get("posX", 0.0)), ABS_POS_SCALE),
1317
+ )
1318
+ _pack_int24_le(
1319
+ buffer,
1320
+ _quantize_signed_int24(float(obj.get("posY", 0.0)), ABS_POS_SCALE),
1321
+ )
1322
+ _pack_int24_le(
1323
+ buffer,
1324
+ _quantize_signed_int24(float(obj.get("posZ", 0.0)), ABS_POS_SCALE),
1325
+ )
1326
+ qx = float(obj.get("rotX", 0.0))
1327
+ qy = float(obj.get("rotY", 0.0))
1328
+ qz = float(obj.get("rotZ", 0.0))
1329
+ qw = float(obj.get("rotW", 1.0))
1330
+ buffer.extend(
1331
+ struct.pack("<I", _compress_quaternion_smallest_three(qx, qy, qz, qw))
1332
+ )
1333
+ return bytes(buffer)
1334
+
1335
+
1336
+ def serialize_object_ownership_changed(
1337
+ object_id: int, new_owner: int, previous_owner: int
1338
+ ) -> bytes:
1339
+ """Serialize ownership changed notification (Server -> Clients via ROUTER)."""
1340
+ buffer = bytearray()
1341
+ buffer.append(MSG_OBJECT_OWNERSHIP_CHANGED)
1342
+ buffer.extend(struct.pack("<I", object_id & 0xFFFFFFFF))
1343
+ buffer.extend(struct.pack("<H", new_owner & 0xFFFF))
1344
+ buffer.extend(struct.pack("<H", previous_owner & 0xFFFF))
1345
+ return bytes(buffer)
1346
+
1347
+
1348
+ def serialize_object_ownership_rejected(
1349
+ object_id: int, current_owner: int, reason_code: int
1350
+ ) -> bytes:
1351
+ """Serialize ownership rejected notification (Server -> Client via ROUTER)."""
1352
+ buffer = bytearray()
1353
+ buffer.append(MSG_OBJECT_OWNERSHIP_REJECTED)
1354
+ buffer.extend(struct.pack("<I", object_id & 0xFFFFFFFF))
1355
+ buffer.extend(struct.pack("<H", current_owner & 0xFFFF))
1356
+ buffer.append(reason_code & 0xFF)
1357
+ return bytes(buffer)
1358
+
1359
+
1360
+ def _deserialize_object_pose(data: bytes, offset: int) -> dict[str, Any]:
1361
+ """Deserialize object pose (Client -> Server)."""
1362
+ result: dict[str, Any] = {}
1363
+ protocol_version = data[offset]
1364
+ offset += 1
1365
+ if protocol_version != PROTOCOL_VERSION:
1366
+ raise ValueError(f"Unsupported protocol version: {protocol_version}")
1367
+ result["objectId"] = struct.unpack("<I", data[offset : offset + 4])[0]
1368
+ offset += 4
1369
+ result["poseSeq"] = struct.unpack("<H", data[offset : offset + 2])[0]
1370
+ offset += 2
1371
+ # Extract body bytes (pos 9B + rot 4B = 13 bytes) for caching
1372
+ body_start = offset
1373
+ px, offset = _unpack_int24_le(data, offset)
1374
+ py, offset = _unpack_int24_le(data, offset)
1375
+ pz, offset = _unpack_int24_le(data, offset)
1376
+ packed_rot = struct.unpack("<I", data[offset : offset + 4])[0]
1377
+ offset += 4
1378
+ result["bodyBytes"] = data[body_start:offset]
1379
+ result["posX"] = _dequantize_signed(px, ABS_POS_SCALE)
1380
+ result["posY"] = _dequantize_signed(py, ABS_POS_SCALE)
1381
+ result["posZ"] = _dequantize_signed(pz, ABS_POS_SCALE)
1382
+ qx, qy, qz, qw = _decompress_quaternion_smallest_three(packed_rot)
1383
+ result["rotX"] = qx
1384
+ result["rotY"] = qy
1385
+ result["rotZ"] = qz
1386
+ result["rotW"] = qw
1387
+ return result
1388
+
1389
+
1390
+ def _deserialize_room_objects(data: bytes, offset: int) -> dict[str, Any]:
1391
+ """Deserialize room objects broadcast."""
1392
+ result: dict[str, Any] = {}
1393
+ protocol_version = data[offset]
1394
+ offset += 1
1395
+ if protocol_version != PROTOCOL_VERSION:
1396
+ raise ValueError(f"Unsupported protocol version: {protocol_version}")
1397
+ result["broadcastTime"] = struct.unpack("<d", data[offset : offset + 8])[0]
1398
+ offset += 8
1399
+ object_count = struct.unpack("<H", data[offset : offset + 2])[0]
1400
+ offset += 2
1401
+ objects: list[dict[str, Any]] = []
1402
+ for _ in range(object_count):
1403
+ obj: dict[str, Any] = {}
1404
+ obj["objectId"] = struct.unpack("<I", data[offset : offset + 4])[0]
1405
+ offset += 4
1406
+ obj["ownerClientNo"] = struct.unpack("<H", data[offset : offset + 2])[0]
1407
+ offset += 2
1408
+ obj["poseSeq"] = struct.unpack("<H", data[offset : offset + 2])[0]
1409
+ offset += 2
1410
+ obj["poseTime"] = struct.unpack("<d", data[offset : offset + 8])[0]
1411
+ offset += 8
1412
+ px, offset = _unpack_int24_le(data, offset)
1413
+ py, offset = _unpack_int24_le(data, offset)
1414
+ pz, offset = _unpack_int24_le(data, offset)
1415
+ packed_rot = struct.unpack("<I", data[offset : offset + 4])[0]
1416
+ offset += 4
1417
+ obj["posX"] = _dequantize_signed(px, ABS_POS_SCALE)
1418
+ obj["posY"] = _dequantize_signed(py, ABS_POS_SCALE)
1419
+ obj["posZ"] = _dequantize_signed(pz, ABS_POS_SCALE)
1420
+ qx, qy, qz, qw = _decompress_quaternion_smallest_three(packed_rot)
1421
+ obj["rotX"] = qx
1422
+ obj["rotY"] = qy
1423
+ obj["rotZ"] = qz
1424
+ obj["rotW"] = qw
1425
+ objects.append(obj)
1426
+ result["objects"] = objects
1427
+ return result
1428
+
1429
+
1430
+ def _deserialize_object_ownership_request(data: bytes, offset: int) -> dict[str, Any]:
1431
+ """Deserialize ownership request (Client -> Server)."""
1432
+ result: dict[str, Any] = {}
1433
+ result["operationType"] = data[offset]
1434
+ offset += 1
1435
+ result["objectId"] = struct.unpack("<I", data[offset : offset + 4])[0]
1436
+ offset += 4
1437
+ return result
1438
+
1439
+
1440
+ def _deserialize_object_ownership_changed(data: bytes, offset: int) -> dict[str, Any]:
1441
+ """Deserialize ownership changed notification."""
1442
+ result: dict[str, Any] = {}
1443
+ result["objectId"] = struct.unpack("<I", data[offset : offset + 4])[0]
1444
+ offset += 4
1445
+ result["newOwnerClientNo"] = struct.unpack("<H", data[offset : offset + 2])[0]
1446
+ offset += 2
1447
+ result["previousOwnerClientNo"] = struct.unpack("<H", data[offset : offset + 2])[0]
1448
+ offset += 2
1449
+ return result
1450
+
1451
+
1452
+ def _deserialize_object_ownership_rejected(data: bytes, offset: int) -> dict[str, Any]:
1453
+ """Deserialize ownership rejected notification."""
1454
+ result: dict[str, Any] = {}
1455
+ result["objectId"] = struct.unpack("<I", data[offset : offset + 4])[0]
1456
+ offset += 4
1457
+ result["currentOwnerClientNo"] = struct.unpack("<H", data[offset : offset + 2])[0]
1458
+ offset += 2
1459
+ result["reasonCode"] = data[offset]
1460
+ offset += 1
1461
+ return result
@@ -51,6 +51,11 @@ from styly_netsync.binary_serializer import (
51
51
  MSG_DEVICE_ID_MAPPING,
52
52
  MSG_GLOBAL_VAR_SET,
53
53
  MSG_GLOBAL_VAR_SYNC,
54
+ MSG_OBJECT_OWNERSHIP_CHANGED,
55
+ MSG_OBJECT_OWNERSHIP_REJECTED,
56
+ MSG_OBJECT_OWNERSHIP_REQUEST,
57
+ MSG_OBJECT_POSE,
58
+ MSG_ROOM_OBJECTS,
54
59
  MSG_ROOM_POSE,
55
60
  MSG_RPC,
56
61
  deserialize,
@@ -90,6 +95,11 @@ MESSAGE_TYPE_NAMES: dict[int, str] = {
90
95
  MSG_GLOBAL_VAR_SYNC: "GLOBAL_VAR_SYNC",
91
96
  MSG_CLIENT_VAR_SET: "CLIENT_VAR_SET",
92
97
  MSG_CLIENT_VAR_SYNC: "CLIENT_VAR_SYNC",
98
+ MSG_OBJECT_POSE: "OBJECT_POSE",
99
+ MSG_ROOM_OBJECTS: "ROOM_OBJECTS",
100
+ MSG_OBJECT_OWNERSHIP_REQUEST: "OBJECT_OWNERSHIP_REQUEST",
101
+ MSG_OBJECT_OWNERSHIP_CHANGED: "OBJECT_OWNERSHIP_CHANGED",
102
+ MSG_OBJECT_OWNERSHIP_REJECTED: "OBJECT_OWNERSHIP_REJECTED",
93
103
  }
94
104
 
95
105
  # ============================================================================