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

282 lines
7.8 KiB
JavaScript
Raw Normal View History

import {performance} from 'perf_hooks';
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
2019-05-05 04:26:23 -05:00
static type() {
return 'informed';
}
2019-05-06 04:04:03 -05:00
constructor(entity, params, state) {
super(entity, params, state);
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-04-30 18:23:53 -05:00
this._rememberedEntities = {};
this._state = this._state.clear();
2019-03-20 23:22:54 -05:00
}
2019-04-28 22:35:20 -05:00
get areaToInform() {
// Reduce entity list to visible.
const room = this.entity.room;
const camera = this.entity.camera;
// Blow up camera rectangle to compensate for camera desync.
const size = Rectangle.size(camera.rectangle);
return Rectangle.expand(
camera.rectangle,
Vector.scale(size, 0.5),
);
}
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),
}));
}
});
}
2019-04-19 23:45:08 -05:00
entityStepFilter(op) {
2019-04-16 21:40:05 -05:00
return (step) => {
if (op !== step.get('op')) {
2019-04-19 23:45:08 -05:00
return false;
2019-04-16 21:40:05 -05:00
}
const parts = step.get('parts');
2019-04-19 23:42:53 -05:00
if (!parts) {
2019-04-19 23:45:08 -05:00
return false;
2019-04-19 23:42:53 -05:00
}
2019-04-16 21:40:05 -05:00
if (6 !== parts.length) {
2019-04-19 23:45:08 -05:00
return false;
2019-04-16 21:40:05 -05:00
}
if ('room' !== parts[1]) {
2019-04-19 23:45:08 -05:00
return false;
2019-04-16 21:40:05 -05:00
}
if ('layers' !== parts[2]) {
2019-04-19 23:45:08 -05:00
return false;
2019-04-16 21:40:05 -05:00
}
if ('entityList' !== parts[4]) {
2019-04-19 23:45:08 -05:00
return false;
2019-04-16 21:40:05 -05:00
}
2019-04-19 23:45:08 -05:00
return true;
2019-04-16 21:40:05 -05:00
};
}
rewriteEntityAdds(state, steps) {
steps = steps.map((step) => {
return step.set('parts', step.get('path').split('/'));
});
2019-04-19 23:45:08 -05:00
const isAdd = this.entityStepFilter('add');
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].entity;
// Reset remembrance timeout.
this._rememberedEntities[uuid].rememberFor = 60;
2019-04-16 21:40:05 -05:00
// 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('/'));
});
2019-04-19 23:45:08 -05:00
const isRemove = this.entityStepFilter('remove');
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] = {
entity: remembered,
// Remember entities for one minute.
rememeberFor: 60,
};
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);
2019-04-28 22:35:20 -05:00
const areaToInform = this.entity.areaToInform;
2019-04-07 14:59:55 -05:00
const room = this.entity.room;
2019-04-12 01:03:42 -05:00
// Write over entity list for every layer.
2019-05-02 20:56:32 -05:00
const layers = room.layers.layers;
for (const index in layers) {
const layer = layers[index];
2019-04-28 22:35:20 -05:00
const visibleEntities = layer.visibleEntities(areaToInform);
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) => {
2019-04-22 14:06:57 -05:00
if (!this._socket) {
return;
}
2019-04-07 14:59:55 -05:00
// 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
},
2019-04-28 22:35:20 -05:00
seesEntity: (entity) => {
const areaToInform = this.entity.areaToInform;
const room = this.entity.room;
const visibleEntities = room.visibleEntities(areaToInform);
return -1 !== visibleEntities.indexOf(entity);
}
2019-03-20 18:35:59 -05:00
};
}
tick(elapsed) {
const rememberedEntities = this._rememberedEntities;
for (const uuid in rememberedEntities) {
const rememberFor = rememberedEntities[uuid].rememberFor -= elapsed;
if (rememberFor <= 0) {
delete rememberedEntities[uuid];
}
}
}
2019-03-20 18:35:59 -05:00
}