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() {
this.worker = new Worker('/server/worker.js', {type: 'module'});
this.worker = new Worker('../server/worker.js', {type: 'module'});
this.worker.onmessage = (event) => {
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() {
this.socket = new WebSocket('/ws');
this.socket = new WebSocket(`ws://${window.location.host}/ws`);
this.socket.onmessage = (event) => {
for (const i in this.listeners) {
this.listeners[i](JSON.parse(event.data));

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import {useContext, useEffect} from 'react';
import addKeyListener from '../add-key-listener.js';
import {RESOLUTION} from '../constants';
import ServerContext from '../context/server';
import ClientContext from '../context/client';
import Dom from './dom';
import Pixi from './pixi';
import styles from './ui.module.css';
@ -23,11 +23,11 @@ const KEY_MAP = {
export default function Ui() {
// Key input.
const server = useContext(ServerContext);
const client = useContext(ClientContext);
useEffect(() => {
return addKeyListener(document.body, ({type, payload}) => {
if (type in KEY_MAP && payload in ACTION_MAP) {
server.send({
client.send({
type: 'action',
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 {
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) {
this.listeners[i](data);
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:
}
}
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);
if (-1 !== index) {
this.listeners.splice(index, 1);
}
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,6 +1,6 @@
import {WebSocketServer} from 'ws';
import Engine from './engine.js';
import Server from './server.js';
const {
SILPHIUS_WEBSOCKET_PORT = 8080
@ -9,7 +9,7 @@ const {
const wss = new WebSocketServer({port: SILPHIUS_WEBSOCKET_PORT});
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)); }
}
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); }
}