Compare commits

...

2 Commits

Author SHA1 Message Date
cha0s
990419c351 refactor: structure 2024-05-27 06:27:08 -05:00
cha0s
29befe969d chore: less 2024-05-27 00:17:03 -05:00
12 changed files with 102 additions and 111 deletions

19
src/client/client.js Normal file
View File

@ -0,0 +1,19 @@
export default class Client {
constructor() {
this.listeners = [];
}
accept(data) {
for (const i in this.listeners) {
this.listeners[i](data);
}
}
addMessageListener(listener) {
this.listeners.push(listener);
}
removeMessageListener(listener) {
const index = this.listeners.indexOf(listener);
if (-1 !== index) {
this.listeners.splice(index, 1);
}
}
}

View File

@ -1,8 +1,8 @@
import Server from './server.js'; import Client from './client.js';
export default class Local extends Server { export default class LocalClient extends Client {
async connect() { async connect() {
this.worker = new Worker('/server/worker.js', {type: 'module'}); this.worker = new Worker('../server/worker.js', {type: 'module'});
this.worker.onmessage = (event) => { this.worker.onmessage = (event) => {
this.accept(event.data); this.accept(event.data);
}; };

View File

@ -1,8 +1,8 @@
import Server from './server.js'; import Client from './client.js';
export default class Remote extends Server { export default class RemoteClient extends Client {
async connect() { async connect() {
this.socket = new WebSocket('/ws'); this.socket = new WebSocket(`ws://${window.location.host}/ws`);
this.socket.onmessage = (event) => { this.socket.onmessage = (event) => {
for (const i in this.listeners) { for (const i in this.listeners) {
this.listeners[i](JSON.parse(event.data)); this.listeners[i](JSON.parse(event.data));

View File

@ -1,9 +1,4 @@
import { import {Sprite} from '@pixi/react';
Sprite,
} from '@pixi/react';
import {useContext, useEffect, useState} from 'react';
import ServerContext from '../context/server';
export default function Entities({entities}) { export default function Entities({entities}) {
return ( return (

View File

@ -5,12 +5,12 @@ import {
import {useContext, useEffect, useState} from 'react'; import {useContext, useEffect, useState} from 'react';
import {RESOLUTION} from '../constants.js'; import {RESOLUTION} from '../constants.js';
import ServerContext from '../context/server'; import ClientContext from '../context/client';
import Entities from './entities'; import Entities from './entities';
import styles from './pixi.module.css'; import styles from './pixi.module.css';
export default function Pixi() { export default function Pixi() {
const server = useContext(ServerContext); const client = useContext(ClientContext);
const [entities, setEntities] = useState({}); const [entities, setEntities] = useState({});
useEffect(() => { useEffect(() => {
function onMessage(message) { function onMessage(message) {
@ -27,9 +27,9 @@ export default function Pixi() {
default: default:
} }
} }
server.addMessageListener(onMessage); client.addMessageListener(onMessage);
return () => { return () => {
server.removeMessageListener(onMessage); client.removeMessageListener(onMessage);
}; };
}); });
return ( return (

View File

@ -1,41 +1,41 @@
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import ServerContext from '../context/server.js'; import ClientContext from '../context/client.js';
import Title from './title'; import Title from './title';
import Ui from './ui'; import Ui from './ui';
export default function Silphius() { export default function Silphius() {
const connectionTuple = useState(); const connectionTuple = useState();
const [server, setServer] = useState(); const [client, setClient] = useState();
useEffect(() => { useEffect(() => {
if (!connectionTuple[0]) { if (!connectionTuple[0]) {
return; return;
} }
async function connect() { async function connect() {
let Server; let Client;
switch (connectionTuple[0]) { switch (connectionTuple[0]) {
case 'local': case 'local':
({default: Server} = await import('../server/local.js')); ({default: Client} = await import('../client/local.js'));
break; break;
case 'remote': case 'remote':
({default: Server} = await import('../server/remote.js')); ({default: Client} = await import('../client/remote.js'));
break; break;
} }
const server = new Server(); const client = new Client();
await server.connect(); await client.connect();
server.send({type: 'connect'}); client.send({type: 'connect'});
setServer(server); setClient(client);
} }
connect(); connect();
}, [connectionTuple[0]]); }, [connectionTuple[0]]);
return ( return (
connectionTuple[0] connectionTuple[0]
? ( ? (
server client
? ( ? (
<ServerContext.Provider value={server}> <ClientContext.Provider value={client}>
<Ui /> <Ui />
</ServerContext.Provider> </ClientContext.Provider>
) )
: null : null
) )

View File

@ -2,7 +2,7 @@ import {useContext, useEffect} from 'react';
import addKeyListener from '../add-key-listener.js'; import addKeyListener from '../add-key-listener.js';
import {RESOLUTION} from '../constants'; import {RESOLUTION} from '../constants';
import ServerContext from '../context/server'; import ClientContext from '../context/client';
import Dom from './dom'; import Dom from './dom';
import Pixi from './pixi'; import Pixi from './pixi';
import styles from './ui.module.css'; import styles from './ui.module.css';
@ -23,11 +23,11 @@ const KEY_MAP = {
export default function Ui() { export default function Ui() {
// Key input. // Key input.
const server = useContext(ServerContext); const client = useContext(ClientContext);
useEffect(() => { useEffect(() => {
return addKeyListener(document.body, ({type, payload}) => { return addKeyListener(document.body, ({type, payload}) => {
if (type in KEY_MAP && payload in ACTION_MAP) { if (type in KEY_MAP && payload in ACTION_MAP) {
server.send({ client.send({
type: 'action', type: 'action',
payload: { payload: {
type: ACTION_MAP[payload], type: ACTION_MAP[payload],

View File

@ -1,65 +0,0 @@
const SPEED = 100;
const TPS = 60;
const MOVE_MAP = {
'moveUp': 0,
'moveRight': 1,
'moveDown': 2,
'moveLeft': 3,
};
export default class Engine {
constructor() {
this.dude = {
image: './assets/bunny.png',
movement: [0, 0, 0, 0],
position: [50, 50],
};
this.frame = 0;
this.last = Date.now();
}
accept({type, payload}) {
switch (type) {
case 'connect': {
this.send({type: 'connected', payload: {dude: this.dude}});
break;
}
case 'action': {
if (payload.type in MOVE_MAP) {
this.dude.movement[MOVE_MAP[payload.type]] = payload.value;
}
break;
}
default:
}
}
send(data) {
throw new Exception('Engine::send is virtual!');
}
start() {
return setInterval(() => {
const elapsed = (Date.now() - this.last) / 1000;
this.last = Date.now();
this.tick(elapsed);
}, 1000 / TPS);
}
tick(elapsed) {
this.dude.position[0] += SPEED * elapsed * (this.dude.movement[1] - this.dude.movement[3]);
this.dude.position[1] += SPEED * elapsed * (this.dude.movement[2] - this.dude.movement[0]);
this.send({
type: 'tick',
payload: {
entities: {dude: this.dude},
elapsed,
frame: this.frame,
},
});
this.frame += 1;
}
}

View File

@ -1,19 +1,61 @@
const SPEED = 100;
const TPS = 60;
const MOVE_MAP = {
'moveUp': 0,
'moveRight': 1,
'moveDown': 2,
'moveLeft': 3,
};
export default class Server { export default class Server {
constructor() { constructor() {
this.listeners = []; this.dude = {
image: './assets/bunny.png',
movement: [0, 0, 0, 0],
position: [50, 50],
};
this.frame = 0;
this.last = Date.now();
} }
accept(data) {
for (const i in this.listeners) { accept({type, payload}) {
this.listeners[i](data); switch (type) {
case 'connect': {
this.send({type: 'connected', payload: {dude: this.dude}});
break;
}
case 'action': {
if (payload.type in MOVE_MAP) {
this.dude.movement[MOVE_MAP[payload.type]] = payload.value;
}
break;
}
default:
} }
} }
addMessageListener(listener) {
this.listeners.push(listener); start() {
return setInterval(() => {
const elapsed = (Date.now() - this.last) / 1000;
this.last = Date.now();
this.tick(elapsed);
}, 1000 / TPS);
} }
removeMessageListener(listener) {
const index = this.listeners.indexOf(listener); tick(elapsed) {
if (-1 !== index) { this.dude.position[0] += SPEED * elapsed * (this.dude.movement[1] - this.dude.movement[3]);
this.listeners.splice(index, 1); this.dude.position[1] += SPEED * elapsed * (this.dude.movement[2] - this.dude.movement[0]);
} this.send({
type: 'tick',
payload: {
entities: {dude: this.dude},
elapsed,
frame: this.frame,
},
});
this.frame += 1;
} }
} }

View File

@ -1,6 +1,6 @@
import {WebSocketServer} from 'ws'; import {WebSocketServer} from 'ws';
import Engine from './engine.js'; import Server from './server.js';
const { const {
SILPHIUS_WEBSOCKET_PORT = 8080 SILPHIUS_WEBSOCKET_PORT = 8080
@ -9,7 +9,7 @@ const {
const wss = new WebSocketServer({port: SILPHIUS_WEBSOCKET_PORT}); const wss = new WebSocketServer({port: SILPHIUS_WEBSOCKET_PORT});
wss.on('connection', function connection(ws) { wss.on('connection', function connection(ws) {
const server = new class WorkerEngine extends Engine { const server = new class SocketServer extends Server {
send(data) { ws.send(JSON.stringify(data)); } send(data) { ws.send(JSON.stringify(data)); }
} }
ws.on('message', function message(data) { ws.on('message', function message(data) {

View File

@ -1,6 +1,6 @@
import Engine from './engine.js'; import Server from './server.js';
const server = new class WorkerEngine extends Engine { const server = new class WorkerServer extends Server {
send(data) { postMessage(data); } send(data) { postMessage(data); }
} }