styly-netsync-server 0.10.4__tar.gz → 0.12.0__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.
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/PKG-INFO +71 -17
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/README.md +70 -16
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/pyproject.toml +1 -1
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/binary_serializer.py +91 -29
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/rest_bridge.py +67 -9
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/server.py +2 -2
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/PKG-INFO +71 -17
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_binary_serializer.py +225 -14
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_rest_bridge.py +153 -7
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/LICENSE +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/setup.cfg +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/__init__.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/__main__.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/adapters.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/cli.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/client.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/client_simulator.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/config.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/default.toml +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/events.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/logging_utils.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/network_utils.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/nv_sync.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/types.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/SOURCES.txt +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/dependency_links.txt +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/entry_points.txt +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/requires.txt +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync_server.egg-info/top_level.txt +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_all_run_methods.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_config.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_discovery_probe.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_logging_cli.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_multi_nic.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_nv_protocol.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_object_sync.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_port_error_message.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_python_client.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_reconnect_identity.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_room_expiry.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/tests/test_stealth_heartbeat.py +0 -0
- {styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/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.
|
|
3
|
+
Version: 0.12.0
|
|
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
|
|
@@ -81,12 +81,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
81
81
|
|
|
82
82
|
## Wire protocol compatibility
|
|
83
83
|
|
|
84
|
-
- Current transform wire protocol is `protocolVersion =
|
|
85
|
-
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact
|
|
86
|
-
-
|
|
84
|
+
- Current transform wire protocol is `protocolVersion = 5`.
|
|
85
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact V5 pose body.
|
|
86
|
+
- v5 adds an optional `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
87
|
+
- Unbound v5 poses keep the v4 `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes vs. v3's 6), so receivers can reconstruct the sender's rig-Y motion.
|
|
88
|
+
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
87
89
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
88
|
-
- Protocol
|
|
89
|
-
- Absolute (`
|
|
90
|
+
- Protocol v5 position quantization ranges:
|
|
91
|
+
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
92
|
+
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
93
|
+
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
90
94
|
- Head-relative (`right/left/virtual`): signed `int16` at `0.005 m` per unit, per-axis range `[-163.84 m, 163.835 m]`.
|
|
91
95
|
- These are encoding limits, not a hard world-size cap. Worlds can be larger, but encoded axis values are clamped if they exceed the representable range.
|
|
92
96
|
|
|
@@ -94,18 +98,18 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
94
98
|
|
|
95
99
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
96
100
|
|
|
97
|
-
Assumed baseline (`protocolVersion=
|
|
98
|
-
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `
|
|
99
|
-
- Room per-client entry (`clientNo + poseTime + clientBody`): `
|
|
101
|
+
Assumed unbound baseline (`protocolVersion=5`, `MovingFloorLocal` off):
|
|
102
|
+
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
103
|
+
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
100
104
|
|
|
101
105
|
| Option | Absolute Position Encoding | Per-axis Range | Client Body Delta | Room Per-client Delta |
|
|
102
106
|
|---|---|---:|---:|---:|
|
|
103
|
-
| A. Coarser scale (current integer width) | `int24 @ 0.02m` | `[-167,772.16m, 167,772.14m]` | `+0B` (`
|
|
104
|
-
| B. Cell + local | `cell(i16, 256m) + local(int24 @ 0.01m)` | `[-8,472,494.08m, 8,472,238.07m]` | `+6B` (`
|
|
105
|
-
| C. Cell + local (large cell) | `cell(i16, 1024m) + local(int24 @ 0.01m)` | `[-33,638,318.08m, 33,637,294.07m]` | `+6B` (`
|
|
107
|
+
| A. Coarser scale (current integer width) | `int24 @ 0.02m` | `[-167,772.16m, 167,772.14m]` | `+0B` (`46 -> 46`) | `+0B` (`56 -> 56`) |
|
|
108
|
+
| B. Cell + local | `cell(i16, 256m) + local(int24 @ 0.01m)` | `[-8,472,494.08m, 8,472,238.07m]` | `+6B` (`46 -> 52`, `+13.0%`) | `+6B` (`56 -> 62`, `+10.7%`) |
|
|
109
|
+
| C. Cell + local (large cell) | `cell(i16, 1024m) + local(int24 @ 0.01m)` | `[-33,638,318.08m, 33,637,294.07m]` | `+6B` (`46 -> 52`, `+13.0%`) | `+6B` (`56 -> 62`, `+10.7%`) |
|
|
106
110
|
|
|
107
111
|
Notes:
|
|
108
|
-
-
|
|
112
|
+
- Only `headPosAbs` is on the wire as an absolute field; `physicalPos` is reconstructed from `headPosAbs + xrOriginDelta`. Option B/C deltas therefore apply to `headPosAbs` only.
|
|
109
113
|
- Option B/C can reduce average overhead if `cell` is transmitted only when changed, but that requires extra state and flags in the wire format.
|
|
110
114
|
|
|
111
115
|
## Configuration
|
|
@@ -160,7 +164,7 @@ The server launches an embedded FastAPI application that exposes REST endpoints
|
|
|
160
164
|
|
|
161
165
|
```json
|
|
162
166
|
{
|
|
163
|
-
"
|
|
167
|
+
"variables": {
|
|
164
168
|
"name": "Jack",
|
|
165
169
|
"lang": "EN"
|
|
166
170
|
}
|
|
@@ -179,11 +183,35 @@ The server launches an embedded FastAPI application that exposes REST endpoints
|
|
|
179
183
|
```bash
|
|
180
184
|
curl -sS -X POST "http://127.0.0.1:8800/v1/rooms/default_room/devices/00000000-0000-0000-0000-000000000000/client-variables" \
|
|
181
185
|
-H "Content-Type: application/json" \
|
|
182
|
-
-d '{"
|
|
186
|
+
-d '{"variables":{"name":"Jack","lang":"EN"}}'
|
|
183
187
|
```
|
|
184
188
|
|
|
185
189
|
The response includes the current mapping status (`clientNo` or `null`) and whether each key was `"applied"` or `"queued"`.
|
|
186
190
|
|
|
191
|
+
- Read endpoints:
|
|
192
|
+
- `GET /v1/rooms/{roomId}/devices/{deviceId}/client-variables` — returns all client variables for the device.
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{"clientNo": 7, "variables": {"name": "Jack", "lang": "EN"}}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
If the device has no `clientNo` mapping yet, returns `{"clientNo": null, "variables": {}}`.
|
|
199
|
+
|
|
200
|
+
- `GET /v1/rooms/{roomId}/devices/{deviceId}/client-variables/{name}` — returns a single variable.
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{"clientNo": 7, "value": "Jack"}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Returns `404` if the device is unmapped or the variable is not set.
|
|
207
|
+
|
|
208
|
+
- Example:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/devices/00000000-0000-0000-0000-000000000000/client-variables"
|
|
212
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/devices/00000000-0000-0000-0000-000000000000/client-variables/name"
|
|
213
|
+
```
|
|
214
|
+
|
|
187
215
|
### Global variables
|
|
188
216
|
|
|
189
217
|
- Endpoint: `POST /v1/rooms/{roomId}/global-variables`
|
|
@@ -191,7 +219,7 @@ The response includes the current mapping status (`clientNo` or `null`) and whet
|
|
|
191
219
|
|
|
192
220
|
```json
|
|
193
221
|
{
|
|
194
|
-
"
|
|
222
|
+
"variables": {
|
|
195
223
|
"score": "42",
|
|
196
224
|
"stage": "lobby"
|
|
197
225
|
}
|
|
@@ -210,7 +238,33 @@ The response includes the current mapping status (`clientNo` or `null`) and whet
|
|
|
210
238
|
```bash
|
|
211
239
|
curl -sS -X POST "http://127.0.0.1:8800/v1/rooms/default_room/global-variables" \
|
|
212
240
|
-H "Content-Type: application/json" \
|
|
213
|
-
-d '{"
|
|
241
|
+
-d '{"variables":{"score":"42","stage":"lobby"}}'
|
|
214
242
|
```
|
|
215
243
|
|
|
216
244
|
The response includes the room ID and whether each key was `"applied"`, `"queued"`, or `"failed"`.
|
|
245
|
+
|
|
246
|
+
- Read endpoints:
|
|
247
|
+
- `GET /v1/rooms/{roomId}/global-variables` — returns all global variables for the room.
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{"variables": {"score": "42", "stage": "lobby"}}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- `GET /v1/rooms/{roomId}/global-variables/{name}` — returns a single variable.
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{"value": "42"}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Returns `404` if the variable is not set.
|
|
260
|
+
|
|
261
|
+
- Example:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/global-variables"
|
|
265
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/global-variables/score"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Read consistency
|
|
269
|
+
|
|
270
|
+
GET endpoints return a snapshot of the REST bridge's in-process cache, which is populated by PUB-SUB broadcasts from the server. The first request to a room lazily creates a bridge and may return an empty snapshot until the initial broadcasts arrive — retry after a short delay if needed.
|
|
@@ -42,12 +42,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
42
42
|
|
|
43
43
|
## Wire protocol compatibility
|
|
44
44
|
|
|
45
|
-
- Current transform wire protocol is `protocolVersion =
|
|
46
|
-
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact
|
|
47
|
-
-
|
|
45
|
+
- Current transform wire protocol is `protocolVersion = 5`.
|
|
46
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact V5 pose body.
|
|
47
|
+
- v5 adds an optional `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
48
|
+
- Unbound v5 poses keep the v4 `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes vs. v3's 6), so receivers can reconstruct the sender's rig-Y motion.
|
|
49
|
+
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
48
50
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
49
|
-
- Protocol
|
|
50
|
-
- Absolute (`
|
|
51
|
+
- Protocol v5 position quantization ranges:
|
|
52
|
+
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
53
|
+
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
54
|
+
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
51
55
|
- Head-relative (`right/left/virtual`): signed `int16` at `0.005 m` per unit, per-axis range `[-163.84 m, 163.835 m]`.
|
|
52
56
|
- These are encoding limits, not a hard world-size cap. Worlds can be larger, but encoded axis values are clamped if they exceed the representable range.
|
|
53
57
|
|
|
@@ -55,18 +59,18 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
55
59
|
|
|
56
60
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
57
61
|
|
|
58
|
-
Assumed baseline (`protocolVersion=
|
|
59
|
-
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `
|
|
60
|
-
- Room per-client entry (`clientNo + poseTime + clientBody`): `
|
|
62
|
+
Assumed unbound baseline (`protocolVersion=5`, `MovingFloorLocal` off):
|
|
63
|
+
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
64
|
+
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
61
65
|
|
|
62
66
|
| Option | Absolute Position Encoding | Per-axis Range | Client Body Delta | Room Per-client Delta |
|
|
63
67
|
|---|---|---:|---:|---:|
|
|
64
|
-
| A. Coarser scale (current integer width) | `int24 @ 0.02m` | `[-167,772.16m, 167,772.14m]` | `+0B` (`
|
|
65
|
-
| B. Cell + local | `cell(i16, 256m) + local(int24 @ 0.01m)` | `[-8,472,494.08m, 8,472,238.07m]` | `+6B` (`
|
|
66
|
-
| C. Cell + local (large cell) | `cell(i16, 1024m) + local(int24 @ 0.01m)` | `[-33,638,318.08m, 33,637,294.07m]` | `+6B` (`
|
|
68
|
+
| A. Coarser scale (current integer width) | `int24 @ 0.02m` | `[-167,772.16m, 167,772.14m]` | `+0B` (`46 -> 46`) | `+0B` (`56 -> 56`) |
|
|
69
|
+
| B. Cell + local | `cell(i16, 256m) + local(int24 @ 0.01m)` | `[-8,472,494.08m, 8,472,238.07m]` | `+6B` (`46 -> 52`, `+13.0%`) | `+6B` (`56 -> 62`, `+10.7%`) |
|
|
70
|
+
| C. Cell + local (large cell) | `cell(i16, 1024m) + local(int24 @ 0.01m)` | `[-33,638,318.08m, 33,637,294.07m]` | `+6B` (`46 -> 52`, `+13.0%`) | `+6B` (`56 -> 62`, `+10.7%`) |
|
|
67
71
|
|
|
68
72
|
Notes:
|
|
69
|
-
-
|
|
73
|
+
- Only `headPosAbs` is on the wire as an absolute field; `physicalPos` is reconstructed from `headPosAbs + xrOriginDelta`. Option B/C deltas therefore apply to `headPosAbs` only.
|
|
70
74
|
- Option B/C can reduce average overhead if `cell` is transmitted only when changed, but that requires extra state and flags in the wire format.
|
|
71
75
|
|
|
72
76
|
## Configuration
|
|
@@ -121,7 +125,7 @@ The server launches an embedded FastAPI application that exposes REST endpoints
|
|
|
121
125
|
|
|
122
126
|
```json
|
|
123
127
|
{
|
|
124
|
-
"
|
|
128
|
+
"variables": {
|
|
125
129
|
"name": "Jack",
|
|
126
130
|
"lang": "EN"
|
|
127
131
|
}
|
|
@@ -140,11 +144,35 @@ The server launches an embedded FastAPI application that exposes REST endpoints
|
|
|
140
144
|
```bash
|
|
141
145
|
curl -sS -X POST "http://127.0.0.1:8800/v1/rooms/default_room/devices/00000000-0000-0000-0000-000000000000/client-variables" \
|
|
142
146
|
-H "Content-Type: application/json" \
|
|
143
|
-
-d '{"
|
|
147
|
+
-d '{"variables":{"name":"Jack","lang":"EN"}}'
|
|
144
148
|
```
|
|
145
149
|
|
|
146
150
|
The response includes the current mapping status (`clientNo` or `null`) and whether each key was `"applied"` or `"queued"`.
|
|
147
151
|
|
|
152
|
+
- Read endpoints:
|
|
153
|
+
- `GET /v1/rooms/{roomId}/devices/{deviceId}/client-variables` — returns all client variables for the device.
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{"clientNo": 7, "variables": {"name": "Jack", "lang": "EN"}}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If the device has no `clientNo` mapping yet, returns `{"clientNo": null, "variables": {}}`.
|
|
160
|
+
|
|
161
|
+
- `GET /v1/rooms/{roomId}/devices/{deviceId}/client-variables/{name}` — returns a single variable.
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{"clientNo": 7, "value": "Jack"}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Returns `404` if the device is unmapped or the variable is not set.
|
|
168
|
+
|
|
169
|
+
- Example:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/devices/00000000-0000-0000-0000-000000000000/client-variables"
|
|
173
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/devices/00000000-0000-0000-0000-000000000000/client-variables/name"
|
|
174
|
+
```
|
|
175
|
+
|
|
148
176
|
### Global variables
|
|
149
177
|
|
|
150
178
|
- Endpoint: `POST /v1/rooms/{roomId}/global-variables`
|
|
@@ -152,7 +180,7 @@ The response includes the current mapping status (`clientNo` or `null`) and whet
|
|
|
152
180
|
|
|
153
181
|
```json
|
|
154
182
|
{
|
|
155
|
-
"
|
|
183
|
+
"variables": {
|
|
156
184
|
"score": "42",
|
|
157
185
|
"stage": "lobby"
|
|
158
186
|
}
|
|
@@ -171,7 +199,33 @@ The response includes the current mapping status (`clientNo` or `null`) and whet
|
|
|
171
199
|
```bash
|
|
172
200
|
curl -sS -X POST "http://127.0.0.1:8800/v1/rooms/default_room/global-variables" \
|
|
173
201
|
-H "Content-Type: application/json" \
|
|
174
|
-
-d '{"
|
|
202
|
+
-d '{"variables":{"score":"42","stage":"lobby"}}'
|
|
175
203
|
```
|
|
176
204
|
|
|
177
205
|
The response includes the room ID and whether each key was `"applied"`, `"queued"`, or `"failed"`.
|
|
206
|
+
|
|
207
|
+
- Read endpoints:
|
|
208
|
+
- `GET /v1/rooms/{roomId}/global-variables` — returns all global variables for the room.
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{"variables": {"score": "42", "stage": "lobby"}}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- `GET /v1/rooms/{roomId}/global-variables/{name}` — returns a single variable.
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{"value": "42"}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Returns `404` if the variable is not set.
|
|
221
|
+
|
|
222
|
+
- Example:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/global-variables"
|
|
226
|
+
curl -sS "http://127.0.0.1:8800/v1/rooms/default_room/global-variables/score"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Read consistency
|
|
230
|
+
|
|
231
|
+
GET endpoints return a snapshot of the REST bridge's in-process cache, which is populated by PUB-SUB broadcasts from the server. The first request to a room lazily creates a bridge and may return an empty snapshot until the initial broadcasts arrive — retry after a short delay if needed.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "styly-netsync-server"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.12.0"
|
|
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"
|
{styly_netsync_server-0.10.4 → styly_netsync_server-0.12.0}/src/styly_netsync/binary_serializer.py
RENAMED
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
8
8
|
# Message type identifiers
|
|
9
|
-
PROTOCOL_VERSION =
|
|
9
|
+
PROTOCOL_VERSION = 5
|
|
10
10
|
MSG_CLIENT_TRANSFORM = 1
|
|
11
11
|
MSG_ROOM_TRANSFORM = 2 # Legacy room transform with short IDs only
|
|
12
12
|
MSG_RPC = 3 # Remote procedure call
|
|
@@ -32,7 +32,7 @@ MSG_OBJECT_OWNERSHIP_REJECTED = 17 # Server → Client (ROUTER): request reject
|
|
|
32
32
|
_max_virtual_transforms = 50
|
|
33
33
|
MAX_VIRTUAL_TRANSFORMS = _max_virtual_transforms # Legacy alias for backward compat
|
|
34
34
|
|
|
35
|
-
# Protocol
|
|
35
|
+
# Protocol v5 transform encoding constants
|
|
36
36
|
ABS_POS_SCALE = 0.01
|
|
37
37
|
LOCO_POS_SCALE = 0.01
|
|
38
38
|
REL_POS_SCALE = 0.005
|
|
@@ -69,6 +69,15 @@ POSE_FLAG_HEAD_VALID = 1 << 2
|
|
|
69
69
|
POSE_FLAG_RIGHT_VALID = 1 << 3
|
|
70
70
|
POSE_FLAG_LEFT_VALID = 1 << 4
|
|
71
71
|
POSE_FLAG_VIRTUALS_VALID = 1 << 5
|
|
72
|
+
POSE_FLAG_MOVING_FLOOR_LOCAL = 1 << 6
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _compute_encoding_flags(flags: int) -> int:
|
|
76
|
+
"""Return pose encoding flags for the sanitized pose flags."""
|
|
77
|
+
encoding_flags = ENCODING_FLAGS_DEFAULT
|
|
78
|
+
if flags & POSE_FLAG_MOVING_FLOOR_LOCAL:
|
|
79
|
+
encoding_flags &= ~ENCODING_PHYSICAL_IS_XRORIGIN_DELTA
|
|
80
|
+
return encoding_flags & 0xFF
|
|
72
81
|
|
|
73
82
|
|
|
74
83
|
def get_max_virtual_transforms() -> int:
|
|
@@ -372,7 +381,7 @@ def _create_transform_dict(
|
|
|
372
381
|
|
|
373
382
|
|
|
374
383
|
def _serialize_client_body(buffer: bytearray, client: dict[str, Any]) -> None:
|
|
375
|
-
"""Serialize a client body in protocol
|
|
384
|
+
"""Serialize a client body in protocol v5 compact format."""
|
|
376
385
|
pose_seq = int(client.get("poseSeq", 0)) & 0xFFFF
|
|
377
386
|
head = client.get("head", {}) or {}
|
|
378
387
|
right = client.get("rightHand", {}) or {}
|
|
@@ -380,6 +389,7 @@ def _serialize_client_body(buffer: bytearray, client: dict[str, Any]) -> None:
|
|
|
380
389
|
virtuals = client.get("virtuals", []) or []
|
|
381
390
|
has_xr_origin_delta = (
|
|
382
391
|
"xrOriginDeltaX" in client
|
|
392
|
+
or "xrOriginDeltaY" in client
|
|
383
393
|
or "xrOriginDeltaZ" in client
|
|
384
394
|
or "xrOriginDeltaYaw" in client
|
|
385
395
|
)
|
|
@@ -410,32 +420,51 @@ def _serialize_client_body(buffer: bytearray, client: dict[str, Any]) -> None:
|
|
|
410
420
|
POSE_FLAG_RIGHT_VALID | POSE_FLAG_LEFT_VALID | POSE_FLAG_VIRTUALS_VALID
|
|
411
421
|
)
|
|
412
422
|
|
|
423
|
+
encoding_flags = _compute_encoding_flags(flags)
|
|
413
424
|
buffer.extend(struct.pack("<H", pose_seq))
|
|
414
425
|
buffer.append(flags)
|
|
415
|
-
buffer.append(
|
|
426
|
+
buffer.append(encoding_flags)
|
|
416
427
|
|
|
417
428
|
physical_valid = bool(flags & POSE_FLAG_PHYSICAL_VALID)
|
|
418
429
|
head_valid = bool(flags & POSE_FLAG_HEAD_VALID)
|
|
419
430
|
right_valid = head_valid and bool(flags & POSE_FLAG_RIGHT_VALID)
|
|
420
431
|
left_valid = head_valid and bool(flags & POSE_FLAG_LEFT_VALID)
|
|
421
432
|
virtual_valid = head_valid and bool(flags & POSE_FLAG_VIRTUALS_VALID)
|
|
433
|
+
moving_floor_local = bool(flags & POSE_FLAG_MOVING_FLOOR_LOCAL)
|
|
422
434
|
|
|
423
435
|
xr_origin_delta_x = float(client.get("xrOriginDeltaX", 0.0))
|
|
436
|
+
xr_origin_delta_y = float(client.get("xrOriginDeltaY", 0.0))
|
|
424
437
|
xr_origin_delta_z = float(client.get("xrOriginDeltaZ", 0.0))
|
|
425
438
|
xr_origin_delta_yaw = float(client.get("xrOriginDeltaYaw", 0.0))
|
|
439
|
+
physical = client.get("physical", {}) or {}
|
|
426
440
|
head_pos = _transform_get_position(head)
|
|
427
441
|
head_rot = _transform_get_quaternion(head)
|
|
428
442
|
head_rot_n = _normalize_quaternion(*head_rot)
|
|
429
443
|
|
|
430
444
|
if physical_valid:
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
445
|
+
if moving_floor_local:
|
|
446
|
+
physical_pos = _transform_get_position(physical)
|
|
447
|
+
physical_rot = _transform_get_quaternion(physical)
|
|
448
|
+
physical_yaw = _quaternion_to_yaw_degrees(*physical_rot)
|
|
449
|
+
buffer.extend(
|
|
450
|
+
struct.pack(
|
|
451
|
+
"<hhhh",
|
|
452
|
+
_quantize_signed(physical_pos[0], LOCO_POS_SCALE),
|
|
453
|
+
_quantize_signed(physical_pos[1], LOCO_POS_SCALE),
|
|
454
|
+
_quantize_signed(physical_pos[2], LOCO_POS_SCALE),
|
|
455
|
+
_quantize_signed(physical_yaw, PHYSICAL_YAW_SCALE),
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
buffer.extend(
|
|
460
|
+
struct.pack(
|
|
461
|
+
"<hhhh",
|
|
462
|
+
_quantize_signed(xr_origin_delta_x, LOCO_POS_SCALE),
|
|
463
|
+
_quantize_signed(xr_origin_delta_y, LOCO_POS_SCALE),
|
|
464
|
+
_quantize_signed(xr_origin_delta_z, LOCO_POS_SCALE),
|
|
465
|
+
_quantize_signed(xr_origin_delta_yaw, PHYSICAL_YAW_SCALE),
|
|
466
|
+
)
|
|
437
467
|
)
|
|
438
|
-
)
|
|
439
468
|
|
|
440
469
|
if head_valid:
|
|
441
470
|
_pack_int24_le(buffer, _quantize_signed_int24(head_pos[0], ABS_POS_SCALE))
|
|
@@ -849,7 +878,7 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
|
|
|
849
878
|
|
|
850
879
|
|
|
851
880
|
def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any], int]:
|
|
852
|
-
"""Deserialize protocol
|
|
881
|
+
"""Deserialize protocol v5 compact pose body."""
|
|
853
882
|
result: dict[str, Any] = {}
|
|
854
883
|
result["poseSeq"] = struct.unpack("<H", data[offset : offset + 2])[0]
|
|
855
884
|
offset += 2
|
|
@@ -865,28 +894,41 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
865
894
|
right_valid = head_valid and bool(flags & POSE_FLAG_RIGHT_VALID)
|
|
866
895
|
left_valid = head_valid and bool(flags & POSE_FLAG_LEFT_VALID)
|
|
867
896
|
virtual_valid = head_valid and bool(flags & POSE_FLAG_VIRTUALS_VALID)
|
|
897
|
+
moving_floor_local = bool(flags & POSE_FLAG_MOVING_FLOOR_LOCAL)
|
|
868
898
|
|
|
869
899
|
physical = _create_transform_dict(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, True)
|
|
870
|
-
head = _create_transform_dict(
|
|
871
|
-
|
|
872
|
-
|
|
900
|
+
head = _create_transform_dict(
|
|
901
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, moving_floor_local
|
|
902
|
+
)
|
|
903
|
+
right = _create_transform_dict(
|
|
904
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, moving_floor_local
|
|
905
|
+
)
|
|
906
|
+
left = _create_transform_dict(
|
|
907
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, moving_floor_local
|
|
908
|
+
)
|
|
873
909
|
|
|
874
910
|
head_pos = (0.0, 0.0, 0.0)
|
|
875
911
|
head_rot = (0.0, 0.0, 0.0, 1.0)
|
|
876
912
|
xr_origin_delta_x = 0.0
|
|
913
|
+
xr_origin_delta_y = 0.0
|
|
877
914
|
xr_origin_delta_z = 0.0
|
|
878
915
|
xr_origin_delta_yaw = 0.0
|
|
879
916
|
|
|
880
917
|
if physical_valid:
|
|
881
|
-
if (
|
|
918
|
+
if (
|
|
919
|
+
not moving_floor_local
|
|
920
|
+
and (encoding_flags & ENCODING_PHYSICAL_IS_XRORIGIN_DELTA) == 0
|
|
921
|
+
):
|
|
882
922
|
raise ValueError(
|
|
883
923
|
"PhysicalValid set but XROrigin delta encoding flag is missing"
|
|
884
924
|
)
|
|
885
|
-
dx_q, dz_q, dyaw_q = struct.unpack("<
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
925
|
+
dx_q, dy_q, dz_q, dyaw_q = struct.unpack("<hhhh", data[offset : offset + 8])
|
|
926
|
+
if not moving_floor_local:
|
|
927
|
+
xr_origin_delta_x = _dequantize_signed(dx_q, LOCO_POS_SCALE)
|
|
928
|
+
xr_origin_delta_y = _dequantize_signed(dy_q, LOCO_POS_SCALE)
|
|
929
|
+
xr_origin_delta_z = _dequantize_signed(dz_q, LOCO_POS_SCALE)
|
|
930
|
+
xr_origin_delta_yaw = _dequantize_signed(dyaw_q, PHYSICAL_YAW_SCALE)
|
|
931
|
+
offset += 8
|
|
890
932
|
|
|
891
933
|
if head_valid:
|
|
892
934
|
hx_q, offset = _unpack_int24_le(data, offset)
|
|
@@ -908,12 +950,31 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
908
950
|
head_rot[1],
|
|
909
951
|
head_rot[2],
|
|
910
952
|
head_rot[3],
|
|
911
|
-
|
|
953
|
+
moving_floor_local,
|
|
912
954
|
)
|
|
913
955
|
|
|
914
|
-
if physical_valid and
|
|
956
|
+
if physical_valid and moving_floor_local:
|
|
957
|
+
physical_pos = (
|
|
958
|
+
_dequantize_signed(dx_q, LOCO_POS_SCALE),
|
|
959
|
+
_dequantize_signed(dy_q, LOCO_POS_SCALE),
|
|
960
|
+
_dequantize_signed(dz_q, LOCO_POS_SCALE),
|
|
961
|
+
)
|
|
962
|
+
physical_rot = _yaw_degrees_to_quaternion(
|
|
963
|
+
_dequantize_signed(dyaw_q, PHYSICAL_YAW_SCALE)
|
|
964
|
+
)
|
|
965
|
+
physical = _create_transform_dict(
|
|
966
|
+
physical_pos[0],
|
|
967
|
+
physical_pos[1],
|
|
968
|
+
physical_pos[2],
|
|
969
|
+
physical_rot[0],
|
|
970
|
+
physical_rot[1],
|
|
971
|
+
physical_rot[2],
|
|
972
|
+
physical_rot[3],
|
|
973
|
+
True,
|
|
974
|
+
)
|
|
975
|
+
elif physical_valid and head_valid:
|
|
915
976
|
translated_x = head_pos[0] - xr_origin_delta_x
|
|
916
|
-
translated_y = head_pos[1]
|
|
977
|
+
translated_y = head_pos[1] - xr_origin_delta_y
|
|
917
978
|
translated_z = head_pos[2] - xr_origin_delta_z
|
|
918
979
|
physical_pos = _rotate_yaw_vector(
|
|
919
980
|
translated_x,
|
|
@@ -965,7 +1026,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
965
1026
|
abs_rot[1],
|
|
966
1027
|
abs_rot[2],
|
|
967
1028
|
abs_rot[3],
|
|
968
|
-
|
|
1029
|
+
moving_floor_local,
|
|
969
1030
|
)
|
|
970
1031
|
|
|
971
1032
|
if left_valid:
|
|
@@ -994,7 +1055,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
994
1055
|
abs_rot[1],
|
|
995
1056
|
abs_rot[2],
|
|
996
1057
|
abs_rot[3],
|
|
997
|
-
|
|
1058
|
+
moving_floor_local,
|
|
998
1059
|
)
|
|
999
1060
|
|
|
1000
1061
|
virtual_count = data[offset]
|
|
@@ -1036,11 +1097,12 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
1036
1097
|
abs_rot[1],
|
|
1037
1098
|
abs_rot[2],
|
|
1038
1099
|
abs_rot[3],
|
|
1039
|
-
|
|
1100
|
+
moving_floor_local,
|
|
1040
1101
|
)
|
|
1041
1102
|
)
|
|
1042
1103
|
|
|
1043
1104
|
result["xrOriginDeltaX"] = xr_origin_delta_x
|
|
1105
|
+
result["xrOriginDeltaY"] = xr_origin_delta_y
|
|
1044
1106
|
result["xrOriginDeltaZ"] = xr_origin_delta_z
|
|
1045
1107
|
result["xrOriginDeltaYaw"] = xr_origin_delta_yaw
|
|
1046
1108
|
result["physical"] = physical
|
|
@@ -1052,7 +1114,7 @@ def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any],
|
|
|
1052
1114
|
|
|
1053
1115
|
|
|
1054
1116
|
def _deserialize_client_transform(data: bytes, offset: int) -> dict[str, Any]:
|
|
1055
|
-
"""Deserialize client pose (
|
|
1117
|
+
"""Deserialize client pose (v5) from binary data."""
|
|
1056
1118
|
result: dict[str, Any] = {}
|
|
1057
1119
|
|
|
1058
1120
|
protocol_version = data[offset]
|
|
@@ -1090,7 +1152,7 @@ def _deserialize_rpc_message(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1090
1152
|
|
|
1091
1153
|
|
|
1092
1154
|
def _deserialize_room_transform(data: bytes, offset: int) -> dict[str, Any]:
|
|
1093
|
-
"""Deserialize room pose (
|
|
1155
|
+
"""Deserialize room pose (v5) with client numbers only."""
|
|
1094
1156
|
result: dict[str, Any] = {}
|
|
1095
1157
|
|
|
1096
1158
|
protocol_version = data[offset]
|