humus-old/server/traits/informed.trait.js

178 lines
5.4 KiB
JavaScript
Raw Normal View History

2019-03-20 18:35:59 -05:00
import * as I from 'immutable';
2019-04-07 14:59:55 -05:00
import immutablediff from 'immutablediff';
2019-03-28 03:39:57 -05:00
import isPlainObject from 'is-plain-object';
2019-03-20 18:35:59 -05:00
2019-03-20 18:58:19 -05:00
import {compose} from '@avocado/core';
2019-04-05 11:59:14 -05:00
import {Trait} from '@avocado/entity';
import {Rectangle, Vector} from '@avocado/math';
2019-04-15 10:03:14 -05:00
import {
Packer,
StateKeysPacket,
StatePacket,
Synchronizer,
} from '@avocado/state';
2019-04-11 15:27:39 -05:00
2019-03-20 18:58:19 -05:00
const decorate = compose(
);
2019-04-05 11:59:14 -05:00
export class Informed extends decorate(Trait) {
2019-03-20 18:35:59 -05:00
initialize() {
2019-04-05 22:40:33 -05:00
this._packer = new Packer();
this._rememberedEntities = {};
this._socket = undefined;
2019-04-07 14:59:55 -05:00
this._state = I.Map();
2019-03-20 18:35:59 -05:00
}
2019-03-20 23:22:54 -05:00
destroy() {
2019-03-21 01:32:59 -05:00
if (this._socket) {
delete this._socket.entity;
delete this._socket;
}
2019-03-20 23:22:54 -05:00
}
2019-04-07 14:59:55 -05:00
reduceState(state) {
// Set client's self entity.
state = state.set('selfEntity', this.entity.instanceUuid);
// Reduce entity list to visible.
const room = this.entity.room;
const camera = this.entity.camera;
2019-04-12 01:03:42 -05:00
// Blow up camera rectangle to compensate for camera desync.
const position = Rectangle.position(camera.rectangle);
const size = Rectangle.size(camera.rectangle);
const visibleArea = Rectangle.compose(
Vector.sub(
position,
Vector.scale(size, 0.25),
),
Vector.scale(size, 1.5),
);
// Write over entity list for every layer.
2019-04-07 14:59:55 -05:00
for (const {index, layer} of room.layers) {
2019-04-12 01:03:42 -05:00
const visibleEntities = layer.visibleEntities(visibleArea);
2019-04-07 14:59:55 -05:00
let reducedEntityList = I.Map();
for (const entity of visibleEntities) {
reducedEntityList = reducedEntityList.set(
entity.instanceUuid,
entity.state,
2019-04-05 12:47:49 -05:00
);
}
2019-04-07 14:59:55 -05:00
const entityListPath = ['room', 'layers', index, 'entityList'];
state = state.setIn(entityListPath, reducedEntityList);
}
2019-04-07 14:59:55 -05:00
return state;
}
get socket() {
return this._socket;
}
set socket(socket) {
socket.entity = this.entity;
this._socket = socket;
}
2019-03-20 18:35:59 -05:00
methods() {
return {
2019-04-07 14:59:55 -05:00
inform: (state) => {
// Reduce state.
const reducedState = this.reduceState(state);
// Take a pure JS diff.
const steps = immutablediff(this._state, reducedState).toJS();
// Rewrite entity removals.
const entityRemovals = steps.filter((step) => {
return 'remove' === step.op && step.path.match(
/\/room\/layers\/.*\/entityList\/[a-z0-9-]+/
);
});
for (const entityRemoval of entityRemovals) {
// Remember the entity.
const parts = entityRemoval.path.split('/');
parts.shift();
const uuid = parts[4];
this._rememberedEntities[uuid] = state.getIn(parts);
// Swap out the removal op for replaces to hide the entity.
const index = steps.indexOf(entityRemoval);
steps.splice(index, 1);
2019-04-12 00:36:44 -05:00
const pathOverrides = [
['physical', 'state', 'addedToPhysics'],
2019-04-14 18:42:23 -05:00
['visible', 'state', 'isVisible'],
// Ticking change last.
['existent', 'state', 'isTicking'],
2019-04-12 00:36:44 -05:00
]
for (const pathOverride of pathOverrides) {
steps.push({
op: 'replace',
path: entityRemoval.path + '/' + pathOverride.join('/'),
value: false,
});
}
}
// Rewrite entity adds.
const entityAdds = steps.filter((step) => {
return 'add' === step.op && step.path.match(
/\/room\/layers\/.*\/entityList\/[a-z0-9-]+/
);
});
for (const entityAdd of entityAdds) {
2019-04-12 00:38:40 -05:00
// Remembered entity?
const parts = entityAdd.path.split('/');
parts.shift();
const uuid = parts[4];
if (!(uuid in this._rememberedEntities)) {
continue;
}
2019-04-12 00:36:44 -05:00
const rememberedEntity = this._rememberedEntities[uuid];
2019-04-12 00:38:40 -05:00
// Take a diff from what the client remembers to now.
const currentState = state.getIn(parts);
const addSteps = immutablediff(
2019-04-12 00:36:44 -05:00
rememberedEntity,
currentState,
).toJS();
2019-04-12 00:38:40 -05:00
// Translate step paths to full state location.
const fullAddSteps = addSteps.map((addStep) => {
return {
op: addStep.op,
path: entityAdd.path + addStep.path,
value: addStep.value,
};
});
// Swap out the add op for replaces to show the entity.
const index = steps.indexOf(entityAdd);
steps.splice(index, 1);
steps.push(...fullAddSteps);
2019-04-12 00:36:44 -05:00
const pathOverrides = [
['physical', 'state', 'addedToPhysics'],
2019-04-14 18:42:23 -05:00
['visible', 'state', 'isVisible'],
// Ticking change last.
['existent', 'state', 'isTicking'],
2019-04-12 00:36:44 -05:00
]
for (const pathOverride of pathOverrides) {
steps.push({
op: 'replace',
path: entityAdd.path + '/' + pathOverride.join('/'),
value: rememberedEntity.getIn(pathOverride),
});
}
delete this._rememberedEntities[uuid];
}
// Remember state.
2019-04-07 14:59:55 -05:00
this._state = reducedState;
if (0 === steps.length) {
return;
2019-04-05 22:50:24 -05:00
}
// Emit!
2019-04-11 12:55:21 -05:00
const keys = this._packer.computeNewKeys(steps);
if (0 !== keys[0].length) {
2019-04-15 10:03:14 -05:00
this._socket.send(new StateKeysPacket(keys));
2019-04-11 12:55:21 -05:00
}
2019-04-07 14:59:55 -05:00
const packed = this._packer.pack(steps);
2019-04-11 15:27:39 -05:00
this._socket.send(new StatePacket(packed));
2019-03-20 18:35:59 -05:00
},
};
}
}