2024-06-14 15:18:55 -05:00
|
|
|
import {json} from "@remix-run/node";
|
2024-06-14 12:27:07 -05:00
|
|
|
import {useEffect, useState} from 'react';
|
|
|
|
import {useOutletContext, useParams} from 'react-router-dom';
|
|
|
|
|
2024-06-27 13:56:43 -05:00
|
|
|
import AssetsContext from '@/context/assets.js';
|
2024-06-14 12:27:07 -05:00
|
|
|
import ClientContext from '@/context/client.js';
|
2024-06-25 08:54:19 -05:00
|
|
|
import DebugContext from '@/context/debug.js';
|
2024-06-22 23:32:57 -05:00
|
|
|
import EcsContext from '@/context/ecs.js';
|
2024-06-21 04:50:17 -05:00
|
|
|
import MainEntityContext from '@/context/main-entity.js';
|
2024-06-22 23:32:57 -05:00
|
|
|
import Ecs from '@/ecs/ecs.js';
|
|
|
|
import Components from '@/ecs-components/index.js';
|
|
|
|
import Systems from '@/ecs-systems/index.js';
|
2024-06-14 12:27:07 -05:00
|
|
|
import Ui from '@/react-components/ui.jsx';
|
2024-06-14 15:18:55 -05:00
|
|
|
import {juggleSession} from '@/session.server';
|
|
|
|
|
2024-06-27 13:56:43 -05:00
|
|
|
import {LRUCache} from 'lru-cache';
|
|
|
|
|
|
|
|
export const cache = new LRUCache({
|
|
|
|
max: 128,
|
|
|
|
});
|
|
|
|
|
2024-06-27 10:53:52 -05:00
|
|
|
class ClientEcs extends Ecs {
|
|
|
|
readAsset(uri) {
|
2024-06-27 13:56:43 -05:00
|
|
|
if (!cache.has(uri)) {
|
|
|
|
let promise, resolve, reject;
|
|
|
|
promise = new Promise((res, rej) => {
|
|
|
|
resolve = res;
|
|
|
|
reject = rej;
|
|
|
|
});
|
|
|
|
cache.set(uri, promise);
|
|
|
|
fetch(new URL(uri, window.location.origin))
|
|
|
|
.then(async (response) => {
|
|
|
|
resolve(response.ok ? response.arrayBuffer() : new ArrayBuffer(0));
|
|
|
|
})
|
|
|
|
.catch(reject);
|
|
|
|
}
|
|
|
|
return cache.get(uri);
|
2024-06-27 10:53:52 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-14 15:18:55 -05:00
|
|
|
export async function loader({request}) {
|
|
|
|
await juggleSession(request);
|
|
|
|
return json({});
|
|
|
|
}
|
2024-06-14 12:27:07 -05:00
|
|
|
|
|
|
|
export default function PlaySpecific() {
|
|
|
|
const Client = useOutletContext();
|
2024-06-27 13:56:43 -05:00
|
|
|
const assetsTuple = useState({});
|
2024-06-14 12:27:07 -05:00
|
|
|
const [client, setClient] = useState();
|
2024-06-21 04:50:17 -05:00
|
|
|
const mainEntityTuple = useState();
|
2024-06-25 08:54:19 -05:00
|
|
|
const debugTuple = useState(false);
|
2024-06-27 10:53:52 -05:00
|
|
|
const ecsTuple = useState(new ClientEcs({Components, Systems}));
|
2024-06-14 12:27:07 -05:00
|
|
|
const [disconnected, setDisconnected] = useState(false);
|
|
|
|
const params = useParams();
|
2024-06-15 20:59:11 -05:00
|
|
|
const [type, url] = params['*'].split('/');
|
2024-06-14 12:27:07 -05:00
|
|
|
useEffect(() => {
|
|
|
|
if (!Client) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const client = new Client();
|
|
|
|
async function connect() {
|
|
|
|
await client.connect(url);
|
|
|
|
setClient(client);
|
|
|
|
}
|
|
|
|
connect();
|
|
|
|
return () => {
|
|
|
|
client.disconnect();
|
|
|
|
};
|
|
|
|
}, [Client, url]);
|
2024-06-15 20:59:11 -05:00
|
|
|
// Sneakily use beforeunload to snag some time to save.
|
|
|
|
useEffect(() => {
|
|
|
|
if ('local' !== type) {
|
|
|
|
return;
|
|
|
|
}
|
2024-06-23 07:35:56 -05:00
|
|
|
async function onBeforeUnload() {
|
2024-06-15 20:59:11 -05:00
|
|
|
client.disconnect();
|
2024-06-21 04:43:32 -05:00
|
|
|
function waitForSave() {
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
|
|
}
|
|
|
|
while (client.worker) { await waitForSave(); }
|
2024-06-15 20:59:11 -05:00
|
|
|
}
|
|
|
|
addEventListener('beforeunload', onBeforeUnload);
|
|
|
|
return () => {
|
|
|
|
removeEventListener('beforeunload', onBeforeUnload);
|
|
|
|
};
|
|
|
|
});
|
2024-06-14 12:27:07 -05:00
|
|
|
useEffect(() => {
|
|
|
|
if (!client) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
function onConnectionStatus(status) {
|
|
|
|
switch (status) {
|
|
|
|
case 'aborted': {
|
|
|
|
setDisconnected(true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'connected': {
|
|
|
|
setDisconnected(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
client.addPacketListener('ConnectionStatus', onConnectionStatus);
|
|
|
|
return () => {
|
|
|
|
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]);
|
2024-06-28 12:07:53 -05:00
|
|
|
useEffect(() => {
|
|
|
|
let source = true;
|
|
|
|
async function play() {
|
|
|
|
const ctx = new AudioContext();
|
|
|
|
const response = await fetch(new URL('/assets/yuff.wav', window.location.origin));
|
|
|
|
const buffer = await ctx.decodeAudioData(await response.arrayBuffer());
|
|
|
|
if (!source) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
source = ctx.createBufferSource();
|
|
|
|
source.buffer = buffer;
|
|
|
|
source.connect(ctx.destination);
|
|
|
|
source.loop = true;
|
|
|
|
source.start();
|
|
|
|
}
|
|
|
|
setTimeout(play, 1000);
|
|
|
|
return () => {
|
|
|
|
if (true !== source) {
|
|
|
|
source.stop();
|
|
|
|
}
|
|
|
|
source = false;
|
|
|
|
};
|
|
|
|
}, [])
|
2024-06-14 12:27:07 -05:00
|
|
|
return (
|
|
|
|
<ClientContext.Provider value={client}>
|
2024-06-21 04:50:17 -05:00
|
|
|
<MainEntityContext.Provider value={mainEntityTuple}>
|
2024-06-22 23:32:57 -05:00
|
|
|
<EcsContext.Provider value={ecsTuple}>
|
2024-06-25 08:54:19 -05:00
|
|
|
<DebugContext.Provider value={debugTuple}>
|
2024-06-27 13:56:43 -05:00
|
|
|
<AssetsContext.Provider value={assetsTuple}>
|
|
|
|
<Ui disconnected={disconnected} />
|
|
|
|
</AssetsContext.Provider>
|
2024-06-25 08:54:19 -05:00
|
|
|
</DebugContext.Provider>
|
2024-06-22 23:32:57 -05:00
|
|
|
</EcsContext.Provider>
|
2024-06-21 04:50:17 -05:00
|
|
|
</MainEntityContext.Provider>
|
2024-06-14 12:27:07 -05:00
|
|
|
</ClientContext.Provider>
|
|
|
|
);
|
|
|
|
}
|