fix: connection semantics, HMR
This commit is contained in:
parent
eb599c60c0
commit
dbe25f86e5
|
@ -39,6 +39,18 @@ export default class Engine {
|
|||
|
||||
incomingActions = [];
|
||||
|
||||
connections = [];
|
||||
|
||||
connectedPlayers = new Map();
|
||||
|
||||
ecses = {};
|
||||
|
||||
frame = 0;
|
||||
|
||||
last = Date.now();
|
||||
|
||||
server;
|
||||
|
||||
constructor(Server) {
|
||||
const ecs = new this.constructor.Ecs();
|
||||
const layerSize = {x: Math.ceil(RESOLUTION.x / 4), y: Math.ceil(RESOLUTION.y / 4)};
|
||||
|
@ -68,10 +80,6 @@ export default class Engine {
|
|||
this.ecses = {
|
||||
1: ecs,
|
||||
};
|
||||
this.connections = [];
|
||||
this.connectedPlayers = new Map();
|
||||
this.frame = 0;
|
||||
this.last = Date.now();
|
||||
class SilphiusServer extends Server {
|
||||
accept(connection, packed) {
|
||||
super.accept(connection, decode(packed));
|
||||
|
|
|
@ -5,9 +5,12 @@ import ClientContext from '@/context/client.js';
|
|||
export default function usePacket(type, fn, dependencies) {
|
||||
const client = useContext(ClientContext);
|
||||
useEffect(() => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
client.addPacketListener(type, fn);
|
||||
return () => {
|
||||
client.removePacketListener(type, fn);
|
||||
};
|
||||
}, dependencies);
|
||||
}, [client, ...dependencies]);
|
||||
}
|
|
@ -15,21 +15,20 @@ onmessage = async (event) => {
|
|||
}
|
||||
socket = new WebSocket(url.href);
|
||||
socket.binaryType = 'arraybuffer';
|
||||
const {promise, resolve, reject} = Promise.withResolvers();
|
||||
const {promise, resolve} = Promise.withResolvers();
|
||||
socket.addEventListener('open', resolve);
|
||||
socket.addEventListener('error', reject);
|
||||
socket.addEventListener('error', () => {
|
||||
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||
close();
|
||||
});
|
||||
await promise;
|
||||
socket.removeEventListener('open', resolve);
|
||||
socket.removeEventListener('error', reject);
|
||||
socket.addEventListener('message', onMessage);
|
||||
socket.addEventListener('close', () => {
|
||||
postMessage(encode({type: 'ConnectionAborted'}));
|
||||
close();
|
||||
});
|
||||
socket.addEventListener('error', () => {
|
||||
postMessage(encode({type: 'ConnectionAborted'}));
|
||||
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||
close();
|
||||
});
|
||||
postMessage(encode({type: 'ConnectionStatus', payload: 'connected'}));
|
||||
connected = true;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {CLIENT_PREDICTION} from '@/constants.js';
|
||||
import {encode} from '@/packets/index.js';
|
||||
|
||||
import Client from './client.js';
|
||||
|
||||
|
@ -30,12 +31,21 @@ export default class RemoteClient extends Client {
|
|||
}
|
||||
this.socket = new WebSocket(url.href);
|
||||
this.socket.binaryType = 'arraybuffer';
|
||||
this.socket.onmessage = (event) => {
|
||||
const onMessage = (event) => {
|
||||
this.accept(event.data);
|
||||
};
|
||||
await new Promise((resolve) => {
|
||||
this.socket.onopen = resolve;
|
||||
}
|
||||
const {promise, resolve} = Promise.withResolvers();
|
||||
this.socket.addEventListener('open', resolve);
|
||||
this.socket.addEventListener('error', () => {
|
||||
this.accept(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||
});
|
||||
await promise;
|
||||
this.socket.removeEventListener('open', resolve);
|
||||
this.socket.addEventListener('message', onMessage);
|
||||
this.socket.addEventListener('close', () => {
|
||||
this.accept(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||
});
|
||||
this.accept(encode({type: 'ConnectionStatus', payload: 'connected'}));
|
||||
}
|
||||
}
|
||||
disconnect() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Engine from '@/engine/engine.js';
|
||||
import Engine from '../../engine/engine.js';
|
||||
import {encode} from '@/packets/index.js';
|
||||
|
||||
import Server from './server.js';
|
||||
|
||||
|
@ -16,4 +17,10 @@ onmessage = (event) => {
|
|||
await engine.load();
|
||||
engine.start();
|
||||
await engine.connectPlayer(undefined);
|
||||
postMessage(encode({type: 'ConnectionStatus', payload: 'connected'}));
|
||||
})();
|
||||
|
||||
import.meta.hot.accept('../../engine/engine.js', () => {
|
||||
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||
close();
|
||||
});
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import Packet from '@/net/packet.js';
|
||||
|
||||
export default class ConnectionAborted extends Packet {}
|
3
app/packets/connection-status.js
Normal file
3
app/packets/connection-status.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Packet from '@/net/packet.js';
|
||||
|
||||
export default class ConnectionStatus extends Packet {}
|
|
@ -1,7 +1,7 @@
|
|||
import {Container} from '@pixi/react';
|
||||
import {useState} from 'react';
|
||||
|
||||
import {RESOLUTION} from '@/constants.js'
|
||||
import {RESOLUTION} from '@/constants.js';
|
||||
import Ecs from '@/engine/ecs.js';
|
||||
import usePacket from '@/hooks/use-packet.js';
|
||||
|
||||
|
@ -25,9 +25,7 @@ export default function EcsComponent() {
|
|||
else {
|
||||
updatedEntities[id] = ecs.get(id);
|
||||
if (updatedEntities[id].MainEntity) {
|
||||
if (!mainEntity) {
|
||||
setMainEntity(ecs.get(id));
|
||||
}
|
||||
setMainEntity(ecs.get(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {useContext, useEffect} from 'react';
|
||||
import {useContext, useEffect, useState} from 'react';
|
||||
|
||||
import addKeyListener from '@/add-key-listener.js';
|
||||
import {ACTION_MAP, RESOLUTION} from '@/constants.js';
|
||||
|
@ -20,6 +20,21 @@ const KEY_MAP = {
|
|||
export default function Ui({disconnected}) {
|
||||
// Key input.
|
||||
const client = useContext(ClientContext);
|
||||
const [showDisconnected, setShowDisconnected] = useState(false);
|
||||
useEffect(() => {
|
||||
let handle;
|
||||
if (disconnected) {
|
||||
handle = setTimeout(() => {
|
||||
setShowDisconnected(true);
|
||||
}, 200);
|
||||
}
|
||||
else {
|
||||
setShowDisconnected(false)
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(handle);
|
||||
};
|
||||
}, [disconnected]);
|
||||
useEffect(() => {
|
||||
return addKeyListener(document.body, ({type, payload}) => {
|
||||
if (type in KEY_MAP && payload in ACTION_MAP) {
|
||||
|
@ -44,7 +59,7 @@ export default function Ui({disconnected}) {
|
|||
<Pixi />
|
||||
<Dom>
|
||||
<HotBar active={0} slots={Array(10).fill(0).map(() => {})} />
|
||||
{disconnected && (
|
||||
{showDisconnected && (
|
||||
<Disconnected />
|
||||
)}
|
||||
</Dom>
|
||||
|
|
|
@ -11,7 +11,7 @@ import styles from './play.module.css';
|
|||
|
||||
export default function Index() {
|
||||
const [client, setClient] = useState();
|
||||
const [disconnected, setDisconnected] = useState();
|
||||
const [disconnected, setDisconnected] = useState(false);
|
||||
const params = useParams();
|
||||
const [type, url] = params['*'].split('/');
|
||||
useEffect(() => {
|
||||
|
@ -46,21 +46,41 @@ export default function Index() {
|
|||
if (!client) {
|
||||
return;
|
||||
}
|
||||
function onConnectionAborted() {
|
||||
setDisconnected(true);
|
||||
function onConnectionStatus(status) {
|
||||
switch (status) {
|
||||
case 'aborted': {
|
||||
setDisconnected(true);
|
||||
break;
|
||||
}
|
||||
case 'connected': {
|
||||
setDisconnected(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
client.addPacketListener('ConnectionAborted', onConnectionAborted);
|
||||
client.addPacketListener('ConnectionStatus', onConnectionStatus);
|
||||
return () => {
|
||||
client.removePacketListener('ConnectionAborted', onConnectionAborted);
|
||||
client.removePacketListener('ConnectionStatus', onConnectionStatus);
|
||||
};
|
||||
}, [client]);
|
||||
useEffect(() => {
|
||||
if (!disconnected) {
|
||||
return;
|
||||
}
|
||||
async function reconnect() {
|
||||
await client.connect(url);
|
||||
}
|
||||
reconnect();
|
||||
const handle = setInterval(reconnect, 1000);
|
||||
return () => {
|
||||
clearInterval(handle);
|
||||
};
|
||||
}, [client, disconnected, url]);
|
||||
return (
|
||||
<div className={styles.play}>
|
||||
{client && (
|
||||
<ClientContext.Provider value={client}>
|
||||
<Ui disconnected={disconnected} />
|
||||
</ClientContext.Provider>
|
||||
)}
|
||||
<ClientContext.Provider value={client}>
|
||||
<Ui disconnected={disconnected} />
|
||||
</ClientContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {WebSocketServer} from 'ws';
|
||||
|
||||
import Engine from '@/engine/engine.js';
|
||||
import Server from '@/net/server/server.js';
|
||||
import Engine from './engine/engine.js';
|
||||
import Server from './net/server/server.js';
|
||||
|
||||
const wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
|
@ -26,20 +26,38 @@ export default async function listen(server) {
|
|||
transmit(ws, packed) { ws.send(packed); }
|
||||
}
|
||||
|
||||
const engine = new Engine(SocketServer);
|
||||
|
||||
await engine.load();
|
||||
engine.start();
|
||||
|
||||
async function onConnect(ws) {
|
||||
ws.on('close', () => {
|
||||
engine.disconnectPlayer(ws);
|
||||
})
|
||||
ws.on('message', (packed) => {
|
||||
engine.server.accept(ws, new DataView(packed.buffer, packed.byteOffset, packed.length));
|
||||
});
|
||||
await engine.connectPlayer(ws);
|
||||
let onConnect;
|
||||
function makeOnConnect(engine) {
|
||||
return async (ws) => {
|
||||
ws.on('close', () => {
|
||||
engine.disconnectPlayer(ws);
|
||||
})
|
||||
ws.on('message', (packed) => {
|
||||
engine.server.accept(ws, new DataView(packed.buffer, packed.byteOffset, packed.length));
|
||||
});
|
||||
await engine.connectPlayer(ws);
|
||||
};
|
||||
}
|
||||
|
||||
wss.on('connection', onConnect);
|
||||
let engine;
|
||||
async function makeEngine(Engine) {
|
||||
const engine = new Engine(SocketServer);
|
||||
await engine.load();
|
||||
engine.start();
|
||||
return engine;
|
||||
}
|
||||
|
||||
engine = await makeEngine(Engine);
|
||||
wss.on('connection', onConnect = makeOnConnect(engine));
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('./engine/engine.js', async ({default: Engine}) => {
|
||||
wss.off('connection', onConnect);
|
||||
for (const [connection] of engine.connectedPlayers) {
|
||||
connection.close();
|
||||
}
|
||||
engine = await makeEngine(Engine);
|
||||
wss.on('connection', onConnect = makeOnConnect(engine));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user