teleportxr 1.0.0

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.
@@ -0,0 +1,422 @@
1
+ 'use strict';
2
+ // using https://github.com/infusion/BitSet.js
3
+ const bit=require("bitset");
4
+ const core=require("../core/core.js");
5
+ const nd=require("../scene/node.js");
6
+ const resources=require("../scene/resources.js");
7
+ const { forEach } = require("underscore");
8
+
9
+ var clientIDToIndex=new Map();
10
+ var nextIndex=0;
11
+
12
+ //! Each resource (node, texture, mesh etc) what MAY need to be streamed has a TrackedResource.
13
+ //! Then, within the TrackedResource class instance, we keep track of which clients:
14
+ //! * Need this resource
15
+ //! * Were sent the resource (and when)
16
+ //! * Acknowledged that the resource was received.
17
+ //! This is done with a bitset: each Client has an index. We set and clear the Client's bit in the
18
+ //! TrackedResource's bitset members to indicate the resource's status with respect to the client.
19
+ //! The exception is sent_server_time_us: this is a Map from client ID to the time sent.
20
+ //! We remove these values when no longer needed, to prevent the maps from getting too large.
21
+ class TrackedResource
22
+ {
23
+ constructor() {
24
+ this.clientNeeds=new bit.BitSet(); // whether we THINK the client NEEDS the resource.
25
+ this.sent=new bit.BitSet(); // Whether we have actually sent the resource,
26
+ this.sent_server_time_us=new Map(); // and when we sent it. Map of clientID to timestamp.
27
+ this.acknowledged=new bit.BitSet(); // Whether the client acknowledged receiving the resource.
28
+ }
29
+ IsNeededByClient(clientID) {
30
+ return this.clientNeeds.get[clientIDToIndex[clientID]];
31
+ }
32
+ WasSentToClient(clientID) {
33
+ return this.sent.get[clientIDToIndex[clientID]];
34
+ }
35
+ WasAcknowledgedByClient(clientID) {
36
+ return this.acknowledged.get(clientIDToIndex[clientID]);
37
+ }
38
+ GetTimeSent(clientID) {
39
+ return this.sent_server_time_us[clientID];
40
+ }
41
+ Sent(clientID,timestamp) {
42
+ this.sent.set(clientIDToIndex[clientID],true);
43
+ this.acknowledged.set(clientIDToIndex[clientID],false);
44
+ this.sent_server_time_us.set(clientID,timestamp);
45
+ }
46
+ AcknowledgeBy(clientID) {
47
+ this.acknowledged.set(clientIDToIndex[clientID],true);
48
+ // erase timestamp?
49
+ this.sent_server_time_us.delete(clientID);
50
+ }
51
+ Timeout(clientID) {
52
+ this.sent.set(clientIDToIndex[clientID],false);
53
+ this.acknowledged.set(clientIDToIndex[clientID],false);
54
+ this.sent_server_time_us.clear(clientID);
55
+ }
56
+ };
57
+
58
+ //! One GeometryService per connected client.
59
+ class GeometryService
60
+ {
61
+ //! One trackedResources shared acrosss all clients.
62
+ static trackedResources=new Map();
63
+
64
+ constructor(clientID) {
65
+ this.clientID=clientID;
66
+ clientIDToIndex.set(clientID,nextIndex++);
67
+ this.originNodeId = 0;
68
+ this.priority = 0;
69
+ // The lowest priority for which the client has confirmed all the nodes we sent.
70
+ // We only send lower-priority nodes when all higher priorities have been confirmed.
71
+ this.lowest_confirmed_node_priority=-100000;
72
+ // How many nodes we have unconfirmed
73
+ this.unconfirmed_priority_counts=new Map();
74
+ // Nodes the client needs, we might not send all at once.
75
+ this.nodesToStreamEventually=new Set();
76
+ //!The nodes actually to stream.
77
+ // When higher priority nodes are acknowledged,
78
+ // lower priority nodes AND their resources are added.
79
+ // This is a map from the resource uid's to the number of REASONS we have to stream it.
80
+ // e.g. if a texture is needed by two nodes, it should have 2 here.
81
+ this.streamedNodes=new Map();
82
+ // Node resources are refcounted, they could be requested
83
+ // by more than one node, and only when no node references
84
+ // them should they be removed.
85
+ this.streamedMeshes=new Map();
86
+ this.streamedMaterials=new Map();
87
+ this.streamedTextures=new Map();
88
+ this.streamedSkeletons=new Map();
89
+ this.streamedBones=new Map();
90
+ this.streamedAnimations=new Map();
91
+ this.streamedTextCanvases=new Map();
92
+ this.streamedFontAtlases=new Map();
93
+
94
+ this.backgroundTextureUid=0;
95
+ }
96
+ SetScene(sc) {
97
+ this.scene=sc;
98
+ }
99
+ SetOriginNode(n_uid) {
100
+ if(this.originNodeId ==n_uid)
101
+ return;
102
+ this.originNodeId = n_uid;-
103
+ this.StreamNode(n_uid);
104
+ }
105
+ static GetOrCreateTrackedResource(uid)
106
+ {
107
+ if(!GeometryService.trackedResources.has(uid))
108
+ GeometryService.trackedResources.set(uid,new TrackedResource());
109
+ var res=GeometryService.trackedResources.get(uid);
110
+ return res;
111
+ }
112
+ StreamNode(uid) {
113
+ // this client should stream node uid.
114
+ var res=GeometryService.GetOrCreateTrackedResource(uid);
115
+ var index=clientIDToIndex.get(this.clientID);
116
+ res.clientNeeds.set(index,true);
117
+ // Add to the list of nodes this client should eventually receive:
118
+ this.nodesToStreamEventually.add(uid);
119
+ }
120
+ UnstreamNode(uid) {
121
+ var index=clientIDToIndex.get(this.clientID);
122
+ if(GeometryService.trackedResources.has(uid))
123
+ {
124
+ var res=GeometryService.GetOrCreateTrackedResource(uid);
125
+ res.clientNeeds.BitSet(index,false);
126
+ }
127
+ // Should certainly be in this set:
128
+ this.nodesToStreamEventually.delete(uid);
129
+ // MAY not be in this set:
130
+ this.streamedNodes.delete(uid);
131
+ // TODO: now reduce the counts for all the dependent resources.
132
+ }
133
+
134
+ AddMeshComponentResources(meshComponent,diff)
135
+ {
136
+ if (meshComponent.getType() != nd.NodeDataType.Mesh)
137
+ {
138
+ return;
139
+ }
140
+ if(meshComponent.data_uid==0)
141
+ {
142
+ return;
143
+ }
144
+ this.streamedMeshes.set(meshComponent.data_uid,this.streamedMeshes.get(meshComponent.data_uid)+diff);
145
+ //meshNode.skeletonID = node.skeletonNodeID;
146
+
147
+ //Get joint/bone IDs, if the skeletonID is not zero.
148
+ if (meshComponent.data_uid != 0 && meshComponent.data_type == nd.NodeDataType.Skeleton)
149
+ {
150
+ var skeleton = geometryStore.getSkeleton(meshComponent.data_uid, getClientAxesStandard());
151
+ for(var uid of skeleton.boneIDs)
152
+ {
153
+ }
154
+ }
155
+ if(meshComponent.renderState.globalIlluminationUid != BigInt(0))
156
+ {
157
+ this.streamedTextures.set(meshComponent.renderState.globalIlluminationUid,this.streamedTextures.get(meshComponent.renderState.globalIlluminationUid)+diff);
158
+ }
159
+ }
160
+
161
+ AddNodeResources(node)
162
+ {
163
+ /*for(var anim_uid of node.animations)
164
+ {
165
+ this.streamedAnimations[anim_uid]+=diff;
166
+ }*/
167
+ for (const material_uid of node.materials)
168
+ {
169
+ var thisMaterial = geometryStore.getMaterial(material_uid);
170
+ if (!thisMaterial)
171
+ {
172
+ continue;
173
+ }
174
+ this.streamedMaterials.set(material_uid,this.streamedMaterials.get(material_uid)+diff);
175
+
176
+ var texture_uids =
177
+ [
178
+ thisMaterial.baseColorTexture.index,
179
+ thisMaterial.metallicRoughnessTexture.index,
180
+ thisMaterial.emissiveTexture.index,
181
+ thisMaterial.normalTexture.index,
182
+ thisMaterial.occlusionTexture.index
183
+ ];
184
+ for(const tex_uid of texture_uids)
185
+ {
186
+ if(tex_uid!=0)
187
+ this.streamedTextures.set(tex_uid,this.streamedTextures.get(tex_uid)+diff);
188
+ }
189
+ }
190
+ }
191
+ AddOrRemoveNodeAndResources(node_uid, diff)
192
+ {
193
+ var already_present=false;
194
+ var old_count=0;
195
+ if(this.streamedNodes.has(node_uid))
196
+ {
197
+ already_present=true;
198
+ old_count=this.streamedNodes.get(node_uid);
199
+ }
200
+ else
201
+ {
202
+ this.streamedNodes.set(node_uid,0);
203
+ }
204
+
205
+ var node = this.scene.GetNode(node_uid);
206
+ console.log("Adding node ",node.name," for client ",this.clientID);
207
+ this.streamedNodes.set(node_uid,old_count+diff);
208
+ var meshResources=[];
209
+ node.components.forEach(component => {
210
+ switch (component.getType())
211
+ {
212
+ case nd.NodeDataType.None:
213
+ case nd.NodeDataType.Light:
214
+ break;
215
+ case nd.NodeDataType.Skeleton:
216
+ {
217
+ //GetSkeletonNodeResources(node_uid, *node, meshResources);
218
+ }
219
+ break;
220
+ case nd.NodeDataType.Mesh:
221
+ {
222
+ this.AddMeshComponentResources(component,diff);
223
+
224
+ /*if(node.skeletonNodeID!=0)
225
+ {
226
+ var skeletonnode = this.scene.getNode(node.skeletonNodeID);
227
+ if(!skeletonnode)
228
+ {
229
+ //TELEPORT_CERR<<"Missing skeleton node "<<node.skeletonNodeID<<std.endl;
230
+ }
231
+ else
232
+ {
233
+ this.streamedNodes[node.skeletonNodeID]+=diff;
234
+ meshResources=meshResources.concat(GetSkeletonNodeResources(node.skeletonNodeID, skeletonnode ));
235
+ for(var r of meshResources)
236
+ {
237
+ for(var b of r.boneIDs)
238
+ {
239
+ if(b)
240
+ streamedNodes.set(b,streamedNodes.get(b)+diff);
241
+ }
242
+ }
243
+ }
244
+ }*/
245
+ }
246
+ break;
247
+ case nd.NodeDataType.TextCanvas:
248
+ if(node.data_uid)
249
+ {
250
+ var textCanvas=this.scene.getTextCanvas(node.data_uid);
251
+ if(c&&c.font_uid)
252
+ {
253
+ var fontAtlas =this.scene.getFontAtlas(c.font_uid);
254
+ if(f)
255
+ {
256
+ if(node.data_uid)
257
+ this.streamedTextCanvases.set(f.data_uid,this.streamedTextCanvases.get(f.data_uid)+diff);
258
+ if(c.font_uid)
259
+ this.streamedFontAtlases.set(f.font_uid,this.streamedFontAtlases.get(f.font_uid)+diff);
260
+ if(f.font_texture_uid)
261
+ this.streamedTextures.set(f.font_texture_uid,this.streamedTextures.get(f.font_texture_uid)+diff);
262
+ }
263
+ }
264
+ }
265
+ break;
266
+ default:
267
+ break;
268
+ }
269
+ });
270
+ }
271
+ /*
272
+
273
+ +-------------------------------------------+
274
+ | nodesToStreamEventually |
275
+ | +---------------------------+ |
276
+ | | streamedNodes | |
277
+ | | +---------------+ | |
278
+ | | | nodesToSend | | |
279
+ | | +---------------+ | |
280
+ | +---------------------------+ |
281
+ +-------------------------------------------+
282
+
283
+ */
284
+
285
+ UpdateNodesToStream()
286
+ {
287
+ // ten seconds for timeout. Tweak this.
288
+ const timeout_us=10000000;
289
+ // The set of ALL the nodes of sufficient priority that the client NEEDS is streamedNodes.
290
+ for(let uid of this.nodesToStreamEventually)
291
+ {
292
+ // If it's not in the global tracked resources list, we can't stream it.
293
+ if(!GeometryService.trackedResources.has(uid))
294
+ continue;
295
+ // The client eventually should need this node.
296
+ // But is it already in the streamed list?
297
+ if(this.streamedNodes.has(uid))
298
+ {
299
+ // no need to add it.
300
+ continue;
301
+ }
302
+ // if it hasn't been sent at all to our client, we add its resources.
303
+ this.AddOrRemoveNodeAndResources(uid,1);
304
+ }
305
+ }
306
+ AddOrRemoveTexture(thisTextureUid,diff){
307
+ if(thisTextureUid==BigInt(0))
308
+ return;
309
+ if(thisTextureUid==0)
310
+ return;
311
+ if(this.streamedTextures.has(thisTextureUid))
312
+ {
313
+
314
+ }
315
+ else
316
+ {
317
+ this.streamedTextures.set(thisTextureUid,0);
318
+ }
319
+ this.streamedTextures.set(thisTextureUid,this.streamedTextures.get(thisTextureUid)+diff);
320
+ }
321
+
322
+ UpdateTexturesToStream() {
323
+ // scene background?
324
+ var bg_uid=resources.GetResourceUidFromUrl(core.GeometryPayloadType.TexturePointer,this.scene.backgroundTexturePath);
325
+ if(this.backgroundTextureUid!=bg_uid) {
326
+ this.AddOrRemoveTexture(this.backgroundTextureUid,-1);
327
+ this.backgroundTextureUid = bg_uid;
328
+ this.AddOrRemoveTexture(this.backgroundTextureUid,1);
329
+ }
330
+ }
331
+ GetResourcesToSend(resourcePool) {
332
+ var toSend=[];
333
+ // We have sets/maps of what the client SHOULD have, but some of these may have been sent already.
334
+ let time_now_us=core.getTimestampUs();
335
+ for(const [uid, count] of resourcePool)
336
+ {
337
+ var res=GeometryService.GetOrCreateTrackedResource(uid);
338
+ // If it was already received we don't send it:
339
+ if(res.WasAcknowledgedByClient(this.clientID))
340
+ continue;
341
+ // But what if it was sent to the client, and not yet acknowledged?
342
+ // depends how long ago.
343
+ if(res.WasSentToClient(this.clientID))
344
+ {
345
+ var timeSentUs=res.GetTimeSent(this.clientID);
346
+ // If we sent it too long ago with no acknowledgement, we can send it again.
347
+ if(time_now_us-timeSentUs>timeout_us)
348
+ {
349
+ res.Timeout(this.clientID);
350
+ }
351
+ else
352
+ {
353
+ continue;
354
+ }
355
+ }
356
+ toSend.push(uid);
357
+ res.Sent(this.clientID,time_now_us);
358
+ }
359
+ return toSend;
360
+ }
361
+
362
+ //! Nodes to send this frame: of the streamedNodes, which have not been sent,
363
+ //! or were sent a while ago and never acknowledged?
364
+ GetNodesToSend() {
365
+ this.UpdateNodesToStream();
366
+ return this.GetResourcesToSend(this.streamedNodes);
367
+ }
368
+ GetTexturesToSend(){
369
+ this.UpdateTexturesToStream();
370
+ return this.GetResourcesToSend(this.streamedTextures);
371
+ }
372
+
373
+ // Get the list of meshes to stream. This is the list of meshes that we should have on the client
374
+ // excluding those that have been sent.
375
+ GetMeshesToStream()
376
+ {
377
+ var resource_uids=[];
378
+ this.streamedMeshes.forEach(uid => {
379
+ //is mesh streamed
380
+ if(!GeometryService.trackedResources.has(uid))
381
+ return;
382
+ var res=GeometryService.GetOrCreateTrackedResource(uid);
383
+ res.Sent(this.clientID,time_now_us);
384
+ if(res.WasSentToClient(this.clientID))
385
+ {
386
+ var timeSentUs=res.GetTimeSent(this.clientID);
387
+ // If we sent it too long ago with no acknowledgement, we can send it again.
388
+ if(time_now_us-timeSentUs>timeout_us)
389
+ {
390
+ res.Timeout(this.clientID);
391
+ }
392
+ }
393
+ else
394
+ {
395
+ // if it hasn't been sent at all to our client, we add its resources.
396
+ resource_uids.push(uid);
397
+ }
398
+ });
399
+ return resource_uids;
400
+ }
401
+ EncodedResource(resource_uid)
402
+ {
403
+ if(!GeometryService.trackedResources.has(resource_uid))
404
+ return;
405
+ var res=GeometryService.GetOrCreateTrackedResource(resource_uid);
406
+ if(res) {
407
+ let time_now_us=core.getTimestampUs();
408
+ res.Sent(this.clientID,time_now_us);
409
+ }
410
+ }
411
+ ConfirmResource(resource_uid)
412
+ {
413
+ if(!GeometryService.trackedResources.has(resource_uid))
414
+ return;
415
+ var res=GeometryService.GetOrCreateTrackedResource(resource_uid);
416
+ if(res) {
417
+ res.AcknowledgeBy(this.clientID);
418
+ }
419
+ }
420
+ };
421
+
422
+ module.exports= {GeometryService};
File without changes
File without changes