humus-old/server/game.js

165 lines
4.6 KiB
JavaScript
Raw Normal View History

2019-03-20 15:28:18 -05:00
// Node.
2019-04-13 18:42:53 -05:00
import msgpack from 'msgpack-lite';
2019-03-20 15:28:18 -05:00
import {performance} from 'perf_hooks';
// 3rd party.
// 2nd party.
2019-04-28 22:35:20 -05:00
import {EntityPacketSynchronizer} from '@avocado/entity';
2019-04-15 10:03:14 -05:00
import {InputPacket} from '@avocado/input';
2019-04-07 20:04:34 -05:00
import {Synchronizer} from '@avocado/state';
2019-04-09 09:55:25 -05:00
import {Ticker} from '@avocado/timing';
2019-03-27 15:51:58 -05:00
// 1st party.
2019-04-04 07:31:21 -05:00
import {WorldTime} from '../common/world-time';
2019-03-27 15:51:58 -05:00
import {createEntityForConnection} from './create-entity-for-connection';
import {createRoom} from './create-server-room';
2019-03-20 15:28:18 -05:00
// Create game.
2019-04-20 14:16:06 -05:00
export default class Game {
constructor() {
const config = this.readConfig();
2019-04-28 22:35:20 -05:00
// Packets.
this.entityPacketSynchronizer = new EntityPacketSynchronizer();
2019-04-20 14:16:06 -05:00
// Room.
this.room = createRoom();
2019-04-28 22:35:20 -05:00
for (const entity of this.room.allEntities()) {
this.onEntityAddedToRoom(entity);
}
this.room.on('entityAdded', this.onEntityAddedToRoom, this);
2019-04-20 14:16:06 -05:00
// World time. Start at 10 am for testing.
this.worldTime = new WorldTime();
this.worldTime.hour = 10;
// Entity tracking.
this.informables = [];
// State synchronization.
this.synchronizer = new Synchronizer({
room: this.room,
worldTime: this.worldTime,
});
2019-04-22 14:06:57 -05:00
this.informTicker = new Ticker(config.informInterval);
this.informTicker.on('tick', this.inform, this);
2019-04-20 14:16:06 -05:00
// Simulation.
this.mainLoopHandle = setInterval(
this.createMainLoop(),
1000 * config.simulationInterval
);
}
2019-04-22 14:17:54 -05:00
destroy(fn) {
clearInterval(this.mainLoopHandle);
this.room.destroy();
fn();
}
2019-04-28 22:35:20 -05:00
bundleEntityPackets() {
const bundledEntityPackets = new Map();
this.entityPacketSynchronizer.flushPackets((packetEntity, packets) => {
for (let i = 0; i < this.informables.length; ++i) {
const entity = this.informables[i];
if (!entity.seesEntity(packetEntity)) {
return;
}
if (!bundledEntityPackets.has(entity)) {
bundledEntityPackets.set(entity, new Map());
}
for (let j = 0; j < packets.length; j++) {
const packet = packets[j];
const Packet = packet.constructor;
if (!bundledEntityPackets.get(entity).has(Packet)) {
bundledEntityPackets.get(entity).set(Packet, packet);
}
else {
bundledEntityPackets.get(entity).get(Packet).bundleWith(packet);
}
}
}
});
return bundledEntityPackets;
}
2019-04-20 14:16:06 -05:00
acceptConnection(socket) {
2019-03-20 15:28:18 -05:00
// Create and track a new entity for the connection.
const entity = createEntityForConnection(socket);
2019-03-27 15:51:58 -05:00
// Track informables.
2019-04-20 14:16:06 -05:00
this.informables.push(entity);
2019-03-27 15:51:58 -05:00
entity.on('destroyed', () => {
2019-04-20 14:16:06 -05:00
const index = this.informables.indexOf(entity);
2019-03-27 15:51:58 -05:00
if (-1 !== index) {
2019-04-20 14:16:06 -05:00
this.informables.splice(index, 1);
2019-03-27 15:51:58 -05:00
}
});
// Add entity to room.
2019-04-20 14:16:06 -05:00
this.room.addEntityToLayer(entity, 'everything');
2019-04-08 08:01:17 -05:00
// Initial information.
2019-04-20 14:16:06 -05:00
entity.inform(this.synchronizer.state);
2019-03-20 15:28:18 -05:00
// Listen for events.
2019-04-20 14:16:06 -05:00
socket.on('packet', this.createPacketListener(socket));
socket.on('disconnect', this.createDisconnectionListener(socket));
2019-03-20 15:28:18 -05:00
}
2019-04-20 14:16:06 -05:00
2019-04-22 14:17:54 -05:00
createDisconnectionListener(socket) {
const {entity} = socket;
return () => {
if (entity.is('existent')) {
entity.destroy();
}
};
2019-04-20 14:16:06 -05:00
}
createMainLoop() {
let lastTime = performance.now();
return () => {
const now = performance.now();
const elapsed = (now - lastTime) / 1000;
lastTime = now;
// Tick synchronized.
this.synchronizer.tick(elapsed);
// Tick informer.
this.informTicker.tick(elapsed);
}
}
createPacketListener(socket) {
const {entity} = socket;
return (packet) => {
2019-04-28 22:35:20 -05:00
this.entityPacketSynchronizer.acceptPacket(packet);
2019-04-20 14:16:06 -05:00
if (packet instanceof InputPacket) {
entity.inputState = packet.toState();
}
};
2019-04-09 09:55:25 -05:00
}
2019-04-20 14:16:06 -05:00
2019-04-28 22:35:20 -05:00
flushEntityPackets() {
const bundledEntityPackets = this.bundleEntityPackets();
const entities = Array.from(bundledEntityPackets.keys());
for (let i = 0; i < entities.length; i++) {
const entity = entities[i];
const packets = Array.from(bundledEntityPackets.get(entity).values());
for (let j = 0; j < packets.length; j++) {
const packet = packets[j];
entity.socket.send(packet);
}
}
}
2019-04-22 14:06:57 -05:00
inform() {
// Inform entities of the new state.
for (let i = 0; i < this.informables.length; ++i) {
const entity = this.informables[i];
entity.inform(this.synchronizer.state);
}
2019-04-28 22:35:20 -05:00
// Flush entity packets.
this.flushEntityPackets();
}
onEntityAddedToRoom(entity) {
this.entityPacketSynchronizer.trackEntity(entity);
2019-04-22 14:06:57 -05:00
}
2019-04-20 14:16:06 -05:00
readConfig() {
return {
2019-04-23 16:59:17 -05:00
informInterval: 1 / 60,
simulationInterval: 1 / 60,
2019-04-20 14:16:06 -05:00
};
2019-03-20 15:28:18 -05:00
}
2019-04-20 14:16:06 -05:00
2019-03-20 15:28:18 -05:00
}