298 lines
8.3 KiB
JavaScript
298 lines
8.3 KiB
JavaScript
import {performance} from 'perf_hooks';
|
|
|
|
import * as I from 'immutable';
|
|
import immutablediff from 'immutablediff';
|
|
|
|
import {compose} from '@avocado/core';
|
|
import {EntityCreatePacket, EntityPacket, EntityRemovePacket, Trait} from '@avocado/entity';
|
|
import {Rectangle, Vector} from '@avocado/math';
|
|
import {BundlePacket} from '@avocado/net';
|
|
import {Synchronizer} from '@avocado/state';
|
|
|
|
const decorate = compose(
|
|
);
|
|
|
|
export class Informed extends decorate(Trait) {
|
|
|
|
static type() {
|
|
return 'informed';
|
|
}
|
|
|
|
constructor(entity, params, state) {
|
|
super(entity, params, state);
|
|
this.seenEntities = [];
|
|
this._socket = undefined;
|
|
}
|
|
|
|
destroy() {
|
|
if (this._socket) {
|
|
delete this._socket.entity;
|
|
delete this._socket;
|
|
}
|
|
}
|
|
|
|
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),
|
|
);
|
|
}
|
|
|
|
filterInvisibleEntityPackets(packets) {
|
|
return packets.filter((packet) => {
|
|
const entity = packet.entity;
|
|
if (!entity) {
|
|
return true;
|
|
}
|
|
// Removes could be on destroyed entities, so pass them.
|
|
if (packet instanceof EntityRemovePacket) {
|
|
return true;
|
|
}
|
|
return entity.is('visible');
|
|
});
|
|
}
|
|
|
|
filterKnownEntityCreatePackets(packets) {
|
|
return packets.filter((packet) => {
|
|
if (!packet.entity) {
|
|
return true;
|
|
}
|
|
if (!(packet instanceof EntityCreatePacket)) {
|
|
return true;
|
|
}
|
|
return !this.hasSeenEntity(packet.entity);
|
|
});
|
|
}
|
|
|
|
filterOutOfRangeEntityPackets(packets, outOfRangeEntities) {
|
|
return packets.filter((packet) => {
|
|
const entity = packet.entity;
|
|
if (!entity) {
|
|
return true;
|
|
}
|
|
// Send removes even if they're out of range, if client knows about
|
|
// them.
|
|
if (
|
|
packet instanceof EntityRemovePacket
|
|
&& this.hasSeenEntity(entity)
|
|
) {
|
|
return true;
|
|
}
|
|
return -1 === outOfRangeEntities.indexOf(packet.entity);
|
|
});
|
|
}
|
|
|
|
hasSeenEntity(entity) {
|
|
return -1 !== this.seenEntities.indexOf(entity);
|
|
}
|
|
|
|
injectEntityCreatePackets(packets, visibleEntities) {
|
|
// Get a list of all visible but not yet seen entities.
|
|
const visibleButNotYetSeen = [];
|
|
for (let i = 0; i < visibleEntities.length; i++) {
|
|
const entity = visibleEntities[i];
|
|
if (!this.hasSeenEntity(entity)) {
|
|
visibleButNotYetSeen.push(entity);
|
|
}
|
|
}
|
|
// Get a list of all existing created entities.
|
|
const allExistingCreatedEntities = packets.filter((packet) => {
|
|
return packet instanceof EntityCreatePacket;
|
|
}).map((packet) => {
|
|
return packet.entity;
|
|
})
|
|
// JIT inject creates before any unknown updates.
|
|
for (let i = 0; i < packets.length; i++) {
|
|
const packet = packets[i];
|
|
// Only care about entity packets.
|
|
if (!(packet instanceof EntityPacket)) {
|
|
continue;
|
|
}
|
|
// Only unknown.
|
|
const entity = packet.entity;
|
|
if (this.hasSeenEntity(entity)) {
|
|
continue;
|
|
}
|
|
if (-1 !== allExistingCreatedEntities.indexOf(entity)) {
|
|
continue;
|
|
}
|
|
// Not creates nor removes.
|
|
if (
|
|
packet instanceof EntityCreatePacket
|
|
|| packet instanceof EntityRemovePacket
|
|
) {
|
|
// This does count as seen.
|
|
const index = visibleButNotYetSeen.indexOf(entity);
|
|
if (-1 !== index) {
|
|
visibleButNotYetSeen.splice(index, 1);
|
|
}
|
|
continue;
|
|
}
|
|
// Inject.
|
|
packets.splice(
|
|
i,
|
|
0,
|
|
new EntityCreatePacket(entity.mergeDiff(), entity)
|
|
);
|
|
i += 1;
|
|
// We've seen it.
|
|
const index = visibleButNotYetSeen.indexOf(entity);
|
|
if (-1 !== index) {
|
|
visibleButNotYetSeen.splice(index, 1);
|
|
}
|
|
allExistingCreatedEntities.push(entity);
|
|
}
|
|
// Append creates for any visible-but-not-yet-seen entities.
|
|
for (let i = 0; i < visibleButNotYetSeen.length; i++) {
|
|
const entity = visibleButNotYetSeen[i];
|
|
// Skip any existing creates.
|
|
if (-1 === allExistingCreatedEntities.indexOf(entity)) {
|
|
packets.push(new EntityCreatePacket(entity.mergeDiff(), entity));
|
|
}
|
|
}
|
|
return packets;
|
|
}
|
|
|
|
injectEntityRemovePackets(packets, visibleEntities) {
|
|
const alreadyRemovedEntities = packets.filter((packet) => {
|
|
return packet instanceof EntityRemovePacket;
|
|
}).map((packet) => {
|
|
return packet.entity;
|
|
});
|
|
for (let i = 0; i < this.seenEntities.length; i++) {
|
|
const entity = this.seenEntities[i];
|
|
if (-1 === visibleEntities.indexOf(entity)) {
|
|
if (-1 === alreadyRemovedEntities.indexOf(entity)) {
|
|
packets.push(new EntityRemovePacket({}, entity));
|
|
}
|
|
}
|
|
}
|
|
return packets;
|
|
}
|
|
|
|
markEntitiesSeen(visibleEntities) {
|
|
for (let i = 0; i < visibleEntities.length; i++) {
|
|
const entity = visibleEntities[i];
|
|
if (-1 === this.seenEntities.indexOf(entity)) {
|
|
this.seenEntities.push(entity);
|
|
}
|
|
}
|
|
}
|
|
|
|
markEntitiesUnseen(packets) {
|
|
const removedEntities = packets.filter((packet) => {
|
|
return packet instanceof EntityRemovePacket;
|
|
}).map((packet) => {
|
|
return packet.entity;
|
|
});
|
|
for (let i = 0; i < removedEntities.length; i++) {
|
|
const entity = removedEntities[i];
|
|
const index = this.seenEntities.indexOf(entity)
|
|
if (-1 !== index) {
|
|
this.seenEntities.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
reducePacketEntitiesByRange(packets) {
|
|
// Unique packet entities.
|
|
const packetEntitiesMap = new Map();
|
|
for (let i = 0; i < packets.length; i++) {
|
|
const entity = packets[i].entity;
|
|
if (entity && !packetEntitiesMap.has(entity)) {
|
|
packetEntitiesMap.set(entity, true);
|
|
}
|
|
}
|
|
// Locate visible entities.
|
|
const areaToInform = this.areaToInform;
|
|
const visibleEntities = this.entity.room.visibleEntities(areaToInform);
|
|
// Document out of range entities.
|
|
const inRangeEntities = [];
|
|
const outOfRangeEntities = [];
|
|
let it = packetEntitiesMap.keys();
|
|
for (let value = it.next(); !value.done; value = it.next()) {
|
|
const entity = value.value;
|
|
if (-1 === visibleEntities.indexOf(entity)) {
|
|
if (-1 === outOfRangeEntities.indexOf(entity)) {
|
|
outOfRangeEntities.push(entity);
|
|
}
|
|
}
|
|
else {
|
|
if (-1 === inRangeEntities.indexOf(entity)) {
|
|
inRangeEntities.push(entity);
|
|
}
|
|
}
|
|
}
|
|
return [inRangeEntities, outOfRangeEntities, visibleEntities];
|
|
}
|
|
|
|
get socket() {
|
|
return this._socket;
|
|
}
|
|
|
|
set socket(socket) {
|
|
socket.entity = this.entity;
|
|
this._socket = socket;
|
|
}
|
|
|
|
methods() {
|
|
return {
|
|
|
|
inform: (packets) => {
|
|
if (0 === packets.length) {
|
|
return;
|
|
}
|
|
|
|
// TODO: filter packets that are only delivered to self entity.
|
|
|
|
// Filter invisible entities.
|
|
packets = this.filterInvisibleEntityPackets(packets);
|
|
// Reduce entities by range.
|
|
const [
|
|
inRangeEntities,
|
|
outOfRangeEntities,
|
|
visibleEntities,
|
|
] = this.reducePacketEntitiesByRange(packets);
|
|
|
|
// TODO? Upgrade seen entity packets that are out of range to entity
|
|
// remembers.
|
|
|
|
// Filter the out of range entity updates.
|
|
packets = this.filterOutOfRangeEntityPackets(
|
|
packets,
|
|
outOfRangeEntities
|
|
);
|
|
// Filter known creates.
|
|
packets = this.filterKnownEntityCreatePackets(packets);
|
|
// Inject create packets.
|
|
packets = this.injectEntityCreatePackets(packets, visibleEntities);
|
|
// Inject removes for any previously seen entity that isn't visible
|
|
// anymore.
|
|
packets = this.injectEntityRemovePackets(packets, visibleEntities);
|
|
// "See" entities.
|
|
this.markEntitiesSeen(visibleEntities);
|
|
// Unsee any removed entities.
|
|
this.markEntitiesUnseen(packets);
|
|
// Ship it!
|
|
if (this._socket) {
|
|
this._socket.send(new BundlePacket(packets));
|
|
}
|
|
},
|
|
|
|
seesEntity: (entity) => {
|
|
return Rectangle.isTouching(
|
|
this.entity.areaToInform,
|
|
entity.visibleAabb
|
|
);
|
|
}
|
|
|
|
};
|
|
}
|
|
|
|
}
|