feat: HMR++
This commit is contained in:
parent
b21ef309aa
commit
26b85f6520
29
app/create-ecs.js
Normal file
29
app/create-ecs.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import Components from '@/ecs-components/index.js';
|
||||
import Systems from '@/ecs-systems/index.js';
|
||||
|
||||
export default function createEcs(Ecs) {
|
||||
const ecs = new Ecs({Components, Systems});
|
||||
const defaultSystems = [
|
||||
'ResetForces',
|
||||
'ApplyControlMovement',
|
||||
'IntegratePhysics',
|
||||
'ClampPositions',
|
||||
'PlantGrowth',
|
||||
'FollowCamera',
|
||||
'VisibleAabbs',
|
||||
'Collliders',
|
||||
'ControlDirection',
|
||||
'SpriteDirection',
|
||||
'RunAnimations',
|
||||
'RunTickingPromises',
|
||||
'Water',
|
||||
'Interactions',
|
||||
];
|
||||
defaultSystems.forEach((defaultSystem) => {
|
||||
const System = ecs.system(defaultSystem);
|
||||
if (System) {
|
||||
System.active = true;
|
||||
}
|
||||
});
|
||||
return ecs;
|
||||
}
|
30
app/create-homestead.js
Normal file
30
app/create-homestead.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import createEcs from './create-ecs.js';
|
||||
|
||||
export default async function createHomestead(Ecs) {
|
||||
const ecs = createEcs(Ecs);
|
||||
const area = {x: 100, y: 60};
|
||||
await ecs.create({
|
||||
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||
Engine: {},
|
||||
TileLayers: {
|
||||
layers: [
|
||||
{
|
||||
area,
|
||||
data: Array(area.x * area.y).fill(0).map(() => 1 + Math.floor(Math.random() * 4)),
|
||||
source: '/assets/tileset.json',
|
||||
tileSize: {x: 16, y: 16},
|
||||
}
|
||||
],
|
||||
},
|
||||
Water: {water: {}},
|
||||
});
|
||||
await ecs.create({
|
||||
Position: {x: 100, y: 100},
|
||||
Sprite: {
|
||||
anchor: {x: 0.5, y: 0.8},
|
||||
source: '/assets/shit-shack/shit-shack.json',
|
||||
},
|
||||
VisibleAabb: {},
|
||||
});
|
||||
return ecs;
|
||||
}
|
50
app/create-player.js
Normal file
50
app/create-player.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
export default async function createPlayer(id) {
|
||||
const player = {
|
||||
Camera: {},
|
||||
Controlled: {},
|
||||
Direction: {direction: 2},
|
||||
Ecs: {path: ['homesteads', `${id}`].join('/')},
|
||||
Emitter: {},
|
||||
Forces: {},
|
||||
Interacts: {},
|
||||
Inventory: {
|
||||
slots: {
|
||||
1: {
|
||||
qty: 100,
|
||||
source: '/assets/potion/potion.json',
|
||||
},
|
||||
2: {
|
||||
qty: 1,
|
||||
source: '/assets/watering-can/watering-can.json',
|
||||
},
|
||||
3: {
|
||||
qty: 1,
|
||||
source: '/assets/tomato-seeds/tomato-seeds.json',
|
||||
},
|
||||
4: {
|
||||
qty: 1,
|
||||
source: '/assets/hoe/hoe.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
Health: {health: 100},
|
||||
Position: {x: 368, y: 368},
|
||||
Speed: {speed: 100},
|
||||
Sound: {},
|
||||
Sprite: {
|
||||
anchor: {x: 0.5, y: 0.8},
|
||||
animation: 'moving:down',
|
||||
frame: 0,
|
||||
frames: 8,
|
||||
source: '/assets/dude/dude.json',
|
||||
speed: 0.115,
|
||||
},
|
||||
Ticking: {},
|
||||
VisibleAabb: {},
|
||||
Wielder: {
|
||||
activeSlot: 0,
|
||||
},
|
||||
};
|
||||
return (new TextEncoder()).encode(JSON.stringify(player));
|
||||
}
|
||||
|
137
app/engine.js
137
app/engine.js
|
@ -3,14 +3,12 @@ import {
|
|||
TPS,
|
||||
} from '@/constants.js';
|
||||
import Ecs from '@/ecs/ecs.js';
|
||||
import Components from '@/ecs-components/index.js';
|
||||
import Systems from '@/ecs-systems/index.js';
|
||||
import {decode, encode} from '@/packets/index.js';
|
||||
import Script from '@/util/script.js';
|
||||
|
||||
function join(...parts) {
|
||||
return parts.join('/');
|
||||
}
|
||||
import createEcs from './create-ecs.js';
|
||||
import createHomestead from './create-homestead.js';
|
||||
import createPlayer from './create-player.js';
|
||||
|
||||
export default class Engine {
|
||||
|
||||
|
@ -126,118 +124,12 @@ export default class Engine {
|
|||
);
|
||||
}
|
||||
|
||||
createEcs() {
|
||||
return new this.Ecs({Components, Systems});
|
||||
}
|
||||
|
||||
async createHomestead(id) {
|
||||
const ecs = this.createEcs();
|
||||
const area = {x: 100, y: 60};
|
||||
await ecs.create({
|
||||
AreaSize: {x: area.x * 16, y: area.y * 16},
|
||||
Engine: {},
|
||||
TileLayers: {
|
||||
layers: [
|
||||
{
|
||||
area,
|
||||
data: Array(area.x * area.y).fill(0).map(() => 1 + Math.floor(Math.random() * 4)),
|
||||
source: '/assets/tileset.json',
|
||||
tileSize: {x: 16, y: 16},
|
||||
}
|
||||
],
|
||||
},
|
||||
Water: {water: {}},
|
||||
});
|
||||
await ecs.create({
|
||||
Position: {x: 100, y: 100},
|
||||
Sprite: {
|
||||
anchor: {x: 0.5, y: 0.8},
|
||||
source: '/assets/shit-shack/shit-shack.json',
|
||||
},
|
||||
VisibleAabb: {},
|
||||
});
|
||||
const defaultSystems = [
|
||||
'ResetForces',
|
||||
'ApplyControlMovement',
|
||||
'IntegratePhysics',
|
||||
'ClampPositions',
|
||||
'PlantGrowth',
|
||||
'FollowCamera',
|
||||
'VisibleAabbs',
|
||||
'Collliders',
|
||||
'ControlDirection',
|
||||
'SpriteDirection',
|
||||
'RunAnimations',
|
||||
'RunTickingPromises',
|
||||
'Water',
|
||||
'Interactions',
|
||||
];
|
||||
defaultSystems.forEach((defaultSystem) => {
|
||||
const System = ecs.system(defaultSystem);
|
||||
if (System) {
|
||||
System.active = true;
|
||||
}
|
||||
});
|
||||
await this.saveEcs(join('homesteads', `${id}`), ecs);
|
||||
}
|
||||
|
||||
async createPlayer(id) {
|
||||
const player = {
|
||||
Camera: {},
|
||||
Controlled: {},
|
||||
Direction: {direction: 2},
|
||||
Ecs: {path: join('homesteads', `${id}`)},
|
||||
Emitter: {},
|
||||
Forces: {},
|
||||
Interacts: {},
|
||||
Inventory: {
|
||||
slots: {
|
||||
// 1: {
|
||||
// qty: 10,
|
||||
// source: '/assets/potion/potion.json',
|
||||
// },
|
||||
2: {
|
||||
qty: 1,
|
||||
source: '/assets/watering-can/watering-can.json',
|
||||
},
|
||||
3: {
|
||||
qty: 1,
|
||||
source: '/assets/tomato-seeds/tomato-seeds.json',
|
||||
},
|
||||
4: {
|
||||
qty: 1,
|
||||
source: '/assets/hoe/hoe.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
Health: {health: 100},
|
||||
Position: {x: 368, y: 368},
|
||||
Speed: {speed: 100},
|
||||
Sound: {},
|
||||
Sprite: {
|
||||
anchor: {x: 0.5, y: 0.8},
|
||||
animation: 'moving:down',
|
||||
frame: 0,
|
||||
frames: 8,
|
||||
source: '/assets/dude/dude.json',
|
||||
speed: 0.115,
|
||||
},
|
||||
Ticking: {},
|
||||
VisibleAabb: {},
|
||||
Wielder: {
|
||||
activeSlot: 0,
|
||||
},
|
||||
};
|
||||
const buffer = (new TextEncoder()).encode(JSON.stringify(player));
|
||||
await this.server.writeData(
|
||||
join('players', `${id}`),
|
||||
buffer,
|
||||
);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async disconnectPlayer(connection) {
|
||||
const {entity, id} = this.connectedPlayers.get(connection);
|
||||
const connectedPlayer = this.connectedPlayers.get(connection);
|
||||
if (!connectedPlayer) {
|
||||
return;
|
||||
}
|
||||
const {entity, id} = connectedPlayer;
|
||||
const ecs = this.ecses[entity.Ecs.path];
|
||||
await this.savePlayer(id, entity);
|
||||
ecs.destroy(entity.id);
|
||||
|
@ -250,7 +142,7 @@ export default class Engine {
|
|||
|
||||
async loadEcs(path) {
|
||||
this.ecses[path] = await this.Ecs.deserialize(
|
||||
this.createEcs(),
|
||||
createEcs(this.Ecs),
|
||||
await this.server.readData(path),
|
||||
);
|
||||
}
|
||||
|
@ -264,8 +156,15 @@ export default class Engine {
|
|||
if ('ENOENT' !== error.code) {
|
||||
throw error;
|
||||
}
|
||||
await this.createHomestead(id);
|
||||
buffer = await this.createPlayer(id);
|
||||
await this.saveEcs(
|
||||
['homesteads', `${id}`].join('/'),
|
||||
await createHomestead(this.Ecs),
|
||||
);
|
||||
buffer = await createPlayer(id);
|
||||
await this.server.writeData(
|
||||
['players', `${id}`].join('/'),
|
||||
buffer,
|
||||
);
|
||||
}
|
||||
return JSON.parse((new TextDecoder()).decode(buffer));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ import {del, get, set} from 'idb-keyval';
|
|||
|
||||
import {encode} from '@/packets/index.js';
|
||||
|
||||
import '../../create-homestead.js';
|
||||
import '../../create-player.js';
|
||||
|
||||
import Engine from '../../engine.js';
|
||||
import Server from './server.js';
|
||||
|
||||
|
@ -57,15 +60,50 @@ onmessage = async (event) => {
|
|||
})();
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('../../engine.js', async ({default: Engine}) => {
|
||||
const createResolver = () => {
|
||||
let r;
|
||||
const promise = new Promise((resolve) => {
|
||||
r = resolve;
|
||||
});
|
||||
promise.resolve = r;
|
||||
return promise;
|
||||
};
|
||||
const beforeResolver = createResolver();
|
||||
const resolvers = [];
|
||||
import.meta.hot.on('vite:beforeUpdate', async () => {
|
||||
engine.stop();
|
||||
await engine.disconnectPlayer(0);
|
||||
if (Engine.prototype.createHomestead.toString() !== engine.createHomestead.toString()) {
|
||||
delete engine.ecses['homesteads/0'];
|
||||
await engine.server.removeData('homesteads/0');
|
||||
const newEngine = new Engine(WorkerServer);
|
||||
await newEngine.createHomestead(0);
|
||||
}
|
||||
beforeResolver.resolve();
|
||||
});
|
||||
import.meta.hot.accept('../../engine.js');
|
||||
import.meta.hot.accept('../../create-player.js', async ({default: createPlayer}) => {
|
||||
const resolver = createResolver();
|
||||
resolvers.push(resolver);
|
||||
await beforeResolver;
|
||||
const oldBuffer = await engine.server.readData('players/0');
|
||||
const oldPlayer = JSON.parse((new TextDecoder()).decode(oldBuffer));
|
||||
const buffer = await createPlayer(0);
|
||||
const player = JSON.parse((new TextDecoder()).decode(buffer));
|
||||
// Less jarring
|
||||
player.Ecs = oldPlayer.Ecs;
|
||||
player.Direction = oldPlayer.Direction;
|
||||
player.Position = oldPlayer.Position;
|
||||
await engine.server.writeData('players/0', (new TextEncoder()).encode(JSON.stringify(player)));
|
||||
resolver.resolve();
|
||||
});
|
||||
import.meta.hot.accept('../../create-homestead.js', async ({default: createHomestead}) => {
|
||||
const resolver = createResolver();
|
||||
resolvers.push(resolver);
|
||||
await beforeResolver;
|
||||
delete engine.ecses['homesteads/0'];
|
||||
await engine.server.removeData('homesteads/0');
|
||||
await engine.saveEcs('homesteads/0', await createHomestead(engine.Ecs));
|
||||
resolver.resolve();
|
||||
});
|
||||
import.meta.hot.on('vite:afterUpdate', async () => {
|
||||
await Promise.all(resolvers);
|
||||
postMessage(encode({type: 'ConnectionStatus', payload: 'aborted'}));
|
||||
close();
|
||||
});
|
||||
import.meta.hot.accept();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ export default function Ecs({scale}) {
|
|||
const [entities, setEntities] = useState({});
|
||||
const [mainEntity] = useMainEntity();
|
||||
useEcsTick((payload) => {
|
||||
if (!ecs) {
|
||||
return;
|
||||
}
|
||||
const updatedEntities = {...entities};
|
||||
for (const id in payload) {
|
||||
const update = payload[id];
|
||||
|
@ -34,10 +37,13 @@ export default function Ecs({scale}) {
|
|||
}
|
||||
setEntities(updatedEntities);
|
||||
}, [ecs, entities, mainEntity]);
|
||||
if (!mainEntity) {
|
||||
if (!ecs || !mainEntity) {
|
||||
return false;
|
||||
}
|
||||
const entity = ecs.get(mainEntity);
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
const {Direction, Position, Wielder} = entity;
|
||||
const projected = Wielder.activeItem()?.project(Position.tile, Direction.direction)
|
||||
const {Camera} = entity;
|
||||
|
|
|
@ -173,7 +173,7 @@ export default function Ui({disconnected}) {
|
|||
for (const listener of client.listeners[':Ecs'] ?? []) {
|
||||
listener(payload.ecs);
|
||||
}
|
||||
}, [hotbarSlots, mainEntity, setMainEntity]);
|
||||
}, [ecs]);
|
||||
useEcsTick((payload) => {
|
||||
let localMainEntity = mainEntity;
|
||||
for (const id in payload) {
|
||||
|
@ -201,7 +201,7 @@ export default function Ui({disconnected}) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, [hotbarSlots, mainEntity, setMainEntity]);
|
||||
}, [ecs, mainEntity]);
|
||||
useEffect(() => {
|
||||
function onContextMenu(event) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -8,15 +8,13 @@ import DebugContext from '@/context/debug.js';
|
|||
import EcsContext from '@/context/ecs.js';
|
||||
import MainEntityContext from '@/context/main-entity.js';
|
||||
import Ecs from '@/ecs/ecs.js';
|
||||
import Components from '@/ecs-components/index.js';
|
||||
import Systems from '@/ecs-systems/index.js';
|
||||
import Ui from '@/react-components/ui.jsx';
|
||||
import {juggleSession} from '@/session.server';
|
||||
import Script from '@/util/script.js';
|
||||
|
||||
import {LRUCache} from 'lru-cache';
|
||||
|
||||
export const cache = new LRUCache({
|
||||
const cache = new LRUCache({
|
||||
max: 128,
|
||||
});
|
||||
|
||||
|
@ -59,11 +57,28 @@ export default function PlaySpecific() {
|
|||
const assetsTuple = useState({});
|
||||
const [client, setClient] = useState();
|
||||
const mainEntityTuple = useState();
|
||||
const setMainEntity = mainEntityTuple[1];
|
||||
const debugTuple = useState(false);
|
||||
const ecsTuple = useState(new ClientEcs({Components, Systems}));
|
||||
const [Components, setComponents] = useState();
|
||||
const [Systems, setSystems] = useState();
|
||||
const ecsTuple = useState();
|
||||
const setEcs = ecsTuple[1];
|
||||
const [disconnected, setDisconnected] = useState(false);
|
||||
const params = useParams();
|
||||
const [type, url] = params['*'].split('/');
|
||||
useEffect(() => {
|
||||
async function setEcsStuff() {
|
||||
const {default: Components} = await import('@/ecs-components/index.js');
|
||||
const {default: Systems} = await import('@/ecs-systems/index.js');
|
||||
setComponents(Components);
|
||||
setSystems(Systems);
|
||||
}
|
||||
setEcsStuff();
|
||||
});
|
||||
useEffect(() => {
|
||||
setEcs(new ClientEcs({Components, Systems}));
|
||||
setDisconnected(true);
|
||||
}, [Components, setEcs, Systems]);
|
||||
useEffect(() => {
|
||||
if (!Client) {
|
||||
return;
|
||||
|
@ -117,9 +132,10 @@ export default function PlaySpecific() {
|
|||
};
|
||||
}, [client]);
|
||||
useEffect(() => {
|
||||
if (!disconnected) {
|
||||
if (!client || !disconnected) {
|
||||
return;
|
||||
}
|
||||
setMainEntity(undefined);
|
||||
async function reconnect() {
|
||||
await client.connect(url);
|
||||
}
|
||||
|
@ -128,7 +144,7 @@ export default function PlaySpecific() {
|
|||
return () => {
|
||||
clearInterval(handle);
|
||||
};
|
||||
}, [client, disconnected, url]);
|
||||
}, [client, disconnected, setMainEntity, url]);
|
||||
useEffect(() => {
|
||||
let source = true;
|
||||
async function play() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user