networked-aframe
Version:
A web framework for building multi-user virtual reality experiences.
225 lines (190 loc) • 6.54 kB
JavaScript
/* global NAF */
var ChildEntityCache = require('./ChildEntityCache');
class NetworkEntities {
constructor() {
this.entities = {};
this.childCache = new ChildEntityCache();
this.onRemoteEntityCreatedEvent = new Event('remoteEntityCreated');
this._persistentFirstSyncs = {};
}
registerEntity(networkId, entity) {
this.entities[networkId] = entity;
}
createRemoteEntity(entityData) {
NAF.log.write('Creating remote entity', entityData);
var networkId = entityData.networkId;
var el = NAF.schemas.getCachedTemplate(entityData.template);
this.initPosition(el, entityData.components);
this.initRotation(el, entityData.components);
this.addNetworkComponent(el, entityData);
this.registerEntity(networkId, el);
return el;
}
initPosition(entity, componentData) {
var hasPosition = componentData['position'];
if (hasPosition) {
var position = componentData.position;
entity.setAttribute('position', position);
}
}
initRotation(entity, componentData) {
var hasRotation = componentData['rotation'];
if (hasRotation) {
var rotation = componentData.rotation;
entity.setAttribute('rotation', rotation);
}
}
addNetworkComponent(entity, entityData) {
var networkData = {
template: entityData.template,
creator: entityData.creator,
owner: entityData.owner,
networkId: entityData.networkId,
persistent: entityData.persistent
};
entity.setAttribute('networked', networkData);
entity.firstUpdateData = entityData;
}
updateEntityMulti(client, dataType, entityDatas, source) {
if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
for (let i = 0, l = entityDatas.d.length; i < l; i++) {
this.updateEntity(client, 'u', entityDatas.d[i], source);
}
}
updateEntity(client, dataType, entityData, source) {
if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
var networkId = entityData.networkId;
if (this.hasEntity(networkId)) {
this.entities[networkId].components.networked.networkUpdate(entityData);
} else if (entityData.isFirstSync && NAF.connection.activeDataChannels[entityData.owner] !== false) {
if (NAF.options.firstSyncSource && source !== NAF.options.firstSyncSource) {
NAF.log.write('Ignoring first sync from disallowed source', source);
} else {
if (entityData.persistent) {
// If we receive a firstSync for a persistent entity that we don't have yet,
// we assume the scene will create it at some point, so stash the update for later use.
this._persistentFirstSyncs[networkId] = entityData;
} else {
this.receiveFirstUpdateFromEntity(entityData);
}
}
}
}
receiveFirstUpdateFromEntity(entityData) {
var parent = entityData.parent;
var networkId = entityData.networkId;
var parentNotCreatedYet = parent && !this.hasEntity(parent);
if (parentNotCreatedYet) {
this.childCache.addChild(parent, entityData);
} else {
var remoteEntity = this.createRemoteEntity(entityData);
this.createAndAppendChildren(networkId, remoteEntity);
this.addEntityToPage(remoteEntity, parent);
}
}
createAndAppendChildren(parentId, parentEntity) {
var children = this.childCache.getChildren(parentId);
for (var i = 0; i < children.length; i++) {
var childEntityData = children[i];
var childId = childEntityData.networkId;
if (this.hasEntity(childId)) {
NAF.log.warn(
'Tried to instantiate entity multiple times',
childId,
childEntityData,
'Existing entity:',
this.getEntity(childId)
);
continue;
}
var childEntity = this.createRemoteEntity(childEntityData);
this.createAndAppendChildren(childId, childEntity);
parentEntity.appendChild(childEntity);
}
}
addEntityToPage(entity, parentId) {
if (this.hasEntity(parentId)) {
this.addEntityToParent(entity, parentId);
} else {
this.addEntityToSceneRoot(entity);
}
}
addEntityToParent(entity, parentId) {
this.entities[parentId].appendChild(entity);
}
addEntityToSceneRoot(el) {
var scene = document.querySelector('a-scene');
scene.appendChild(el);
}
completeSync(targetClientId, isFirstSync) {
for (var id in this.entities) {
if (this.entities[id]) {
this.entities[id].components.networked.syncAll(targetClientId, isFirstSync);
}
}
}
removeRemoteEntity(toClient, dataType, data, source) {
if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
var id = data.networkId;
return this.removeEntity(id);
}
removeEntitiesOfClient(clientId) {
const removedEntities = [];
for (var id in this.entities) {
const entity = this.entities[id]
const creator = NAF.utils.getCreator(entity);
const owner = NAF.utils.getNetworkOwner(entity);
if (creator === clientId || (!creator && owner === clientId)) {
const component = this.entities[id].getAttribute("networked")
if (component && component.persistent) {
// everyone will attempt to take ownership, someone will win, it does not particularly matter who
NAF.utils.takeOwnership(entity);
} else {
removedEntities.push(this.removeEntity(id));
}
}
}
return removedEntities;
}
removeEntity(id) {
this.forgetPersistentFirstSync(id);
if (this.hasEntity(id)) {
var entity = this.entities[id];
this.forgetEntity(id);
entity.parentNode.removeChild(entity);
return entity;
} else {
NAF.log.error("Tried to remove entity I don't have.");
return null;
}
}
forgetEntity(id){
delete this.entities[id];
this.forgetPersistentFirstSync(id);
}
getPersistentFirstSync(id){
return this._persistentFirstSyncs[id];
}
forgetPersistentFirstSync(id){
delete this._persistentFirstSyncs[id];
}
getEntity(id) {
if (this.entities[id]) {
return this.entities[id];
}
return null;
}
hasEntity(id) {
return !!this.entities[id];
}
removeRemoteEntities() {
this.childCache = new ChildEntityCache();
for (var id in this.entities) {
var owner = this.entities[id].getAttribute('networked').owner;
if (owner != NAF.clientId) {
this.removeEntity(id);
}
}
}
}
module.exports = NetworkEntities;