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

240 lines
6.7 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-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-16 21:40:05 -05:00
entityOverrides(path, fn) {
const pathOverrides = [
['physical', 'state', 'addedToPhysics'],
['visible', 'state', 'isVisible'],
// Ticking change last.
['existent', 'state', 'isTicking'],
]
return I.List().withMutations((steps) => {
for (const pathOverride of pathOverrides) {
steps.push(I.Map({
op: 'replace',
path: path + '/' + pathOverride.join('/'),
value: fn(pathOverride),
}));
}
});
}
entityStepFilter(op, invert) {
2019-04-16 21:40:05 -05:00
return (step) => {
if (op !== step.get('op')) {
return invert;
2019-04-16 21:40:05 -05:00
}
const parts = step.get('parts');
2019-04-19 23:42:53 -05:00
if (!parts) {
return invert;
}
2019-04-16 21:40:05 -05:00
if (6 !== parts.length) {
return invert;
2019-04-16 21:40:05 -05:00
}
if ('room' !== parts[1]) {
return invert;
2019-04-16 21:40:05 -05:00
}
if ('layers' !== parts[2]) {
return invert;
2019-04-16 21:40:05 -05:00
}
if ('entityList' !== parts[4]) {
return invert;
2019-04-16 21:40:05 -05:00
}
return !invert;
2019-04-16 21:40:05 -05:00
};
}
rewriteEntityAdds(state, steps) {
steps = steps.map((step) => {
return step.set('parts', step.get('path').split('/'));
});
const isAdd = this.entityStepFilter('add', false);
const adds = steps.filter(isAdd);
steps = steps.withMutations((steps) => {
2019-04-16 21:40:05 -05:00
const iterator = adds.values();
for (const add of iterator) {
// Remember the entity.
const parts = add.get('parts');
const layerId = parts[3];
const uuid = parts[5];
if (!(uuid in this._rememberedEntities)) {
continue;
}
const rememberedEntity = this._rememberedEntities[uuid];
// Take a diff from what the client remembers to now.
const currentState = state.getIn(
['room', 'layers', layerId, 'entityList', uuid]
);
const addSteps = immutablediff(rememberedEntity, currentState);
// Translate step paths to full state location.
const fullAddSteps = addSteps.map((addStep) => {
return addStep.set('path', add.get('path') + addStep.get('path'));
});
for (let i = 0; i < fullAddSteps.size; ++i) {
steps.push(fullAddSteps.get(i));
}
2019-04-16 21:40:05 -05:00
// Add overrides.
const overrides = this.entityOverrides(
add.get('path'),
(path) => rememberedEntity.getIn(path),
);
for (let i = 0; i < overrides.size; ++i) {
steps.push(overrides.get(i));
}
2019-04-16 21:40:05 -05:00
}
});
// Filter adds for remembered entities.
steps = steps.filter((step) => {
if (!isAdd(step)) {
return true;
2019-04-16 21:40:05 -05:00
}
const parts = step.get('parts');
const uuid = parts[5];
return !(uuid in this._rememberedEntities);
2019-04-16 21:40:05 -05:00
});
// Forget all remembered entities.
adds.forEach((step) => {
const parts = step.get('parts');
const uuid = parts[5];
delete this._rememberedEntities[uuid];
});
return steps;
2019-04-16 21:40:05 -05:00
}
rewriteEntityRemovals(state, steps) {
steps = steps.map((step) => {
return step.set('parts', step.get('path').split('/'));
});
const isRemove = this.entityStepFilter('remove', false);
const removals = steps.filter(isRemove);
steps = steps.withMutations((steps) => {
for (const removal of removals.values()) {
2019-04-16 21:40:05 -05:00
// Remember the entity.
const parts = removal.get('parts');
const layerId = parts[3];
const uuid = parts[5];
const remembered = state.getIn(
2019-04-16 21:40:05 -05:00
['room', 'layers', layerId, 'entityList', uuid]
);
// Actually destroyed?
if (!remembered) {
return;
}
this._rememberedEntities[uuid] = remembered;
2019-04-16 21:40:05 -05:00
// Add overrides.
const overrides = this.entityOverrides(
removal.get('path'),
() => false
);
for (let i = 0; i < overrides.size; ++i) {
steps.push(overrides.get(i));
}
2019-04-16 21:40:05 -05:00
}
});
// Remove all removes.
return steps.filter((step) => {
if (!isRemove(step)) {
return true;
}
const parts = step.get('parts');
const uuid = parts[5];
return !(uuid in this._rememberedEntities);
});
2019-04-16 21:40:05 -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 size = Rectangle.size(camera.rectangle);
2019-04-17 00:40:03 -05:00
const visibleArea = Rectangle.expand(
camera.rectangle,
2019-04-19 23:24:20 -05:00
Vector.scale(size, 0.5),
2019-04-12 01:03:42 -05:00
);
// 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-17 00:40:03 -05:00
const reduceEntityListRaw = {};
for (let i = 0; i < visibleEntities.length; ++i) {
const entity = visibleEntities[i];
reduceEntityListRaw[entity.instanceUuid] = entity.state;
}
2019-04-17 00:40:03 -05:00
let reducedEntityList = I.Map(reduceEntityListRaw);
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.
2019-04-16 21:40:05 -05:00
let steps = immutablediff(this._state, reducedState);
2019-04-19 02:49:11 -05:00
if (0 === steps.size) {
2019-04-16 21:40:05 -05:00
this._state = reducedState;
2019-04-07 14:59:55 -05:00
return;
2019-04-05 22:50:24 -05:00
}
2019-04-16 21:40:05 -05:00
// Rewrite entity adds/removals.
steps = this.rewriteEntityAdds(state, steps);
steps = this.rewriteEntityRemovals(state, steps);
// Remember state.
this._state = reducedState;
// 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
},
};
}
}