Improve networking

This commit is contained in:
2026-09-25 00:08:01 -03:00
parent 6458bc0440
commit 2a2ccae9ce
18 changed files with 600 additions and 162 deletions
+26 -15
View File
@@ -8,6 +8,7 @@ import type { OreJson } from "$/common/mod_data.ts";
import { AIR_ID } from "$/common/protocol.ts";
import { Position } from "$/common/components/position.ts";
import type { ClientWorld } from "./client_world.ts";
import { code_url, fetch_verified } from "./handshake.ts";
// what the chunk workers need to generate the same world as the server
export const worldgen_mods: { scripts: { mod: string; url: string }[]; ores: OreJson[] } = { scripts: [], ores: [] };
@@ -19,26 +20,36 @@ export function set_mods_world(client_world: ClientWorld) {
world = client_world;
}
export async function load_client_mods(listings: ModListing[]) {
const site = new URL("/", location.href);
const datas = await Promise.all(listings.map(async (listing) => {
const response = await fetch(new URL(listing.data, site));
if (!response.ok) {
throw new ModLoadError(listing.id, `couldn't download its data (${response.status})`);
}
return { id: listing.id, data: await response.json() as ModData };
// downloads every mod's files and checks them against the hashes the server listed, then registers the data and
// runs the client scripts. everything a mod runs is imported from the checked bytes, never fetched twice
export async function load_client_mods(listings: ModListing[], base: URL) {
const downloads = await Promise.all(listings.map(async (listing) => {
const get = async (path: string | undefined, sha256: string | undefined, what: string) => {
if (!path) return undefined;
try {
return await fetch_verified(new URL(path, base), sha256 ?? "", what);
} catch (e) {
throw new ModLoadError(listing.id, (e as Error).message);
}
};
const [data, client, worldgen] = await Promise.all([
get(listing.data, listing.sha256.data, "its data"),
get(listing.client, listing.sha256.client, "its client script"),
get(listing.worldgen, listing.sha256.worldgen, "its worldgen script"),
]);
return { listing, data: JSON.parse(new TextDecoder().decode(data)) as ModData, client, worldgen };
}));
const recipes = register_mod_data(datas);
const recipes = register_mod_data(downloads.map(({ listing, data }) => ({ id: listing.id, data })));
worldgen_mods.ores = recipes.ores;
worldgen_mods.scripts = listings.flatMap((listing) =>
listing.worldgen ? [{ mod: listing.id, url: new URL(listing.worldgen, site).href }] : []
worldgen_mods.scripts = downloads.flatMap(({ listing, worldgen }) =>
worldgen ? [{ mod: listing.id, url: code_url(worldgen) }] : []
);
for (const listing of listings) {
if (!listing.client) continue;
const module = await import(new URL(listing.client, site).href);
for (const { listing, client } of downloads) {
if (!client) continue;
const module = await import(code_url(client));
if (typeof module.setup !== "function") {
throw new ModLoadError(listing.id, "the client script doesn't export a setup function");
}