From ef1c3375420330faa627749cbde6d8fcdccdc86e Mon Sep 17 00:00:00 2001 From: j0code <42189560+j0code@users.noreply.github.com> Date: Thu, 29 Feb 2024 05:29:57 +0100 Subject: [PATCH] NamespacedId for type safety --- src/Item.ts | 4 ++-- src/block/Block.ts | 4 ++-- src/block/ContainerBlock.ts | 4 ++-- src/defs/Base.ts | 5 +++-- src/entity/Entity.ts | 4 ++-- src/entity/Player.ts | 4 +++- src/main.ts | 9 +++++---- src/util/interfaces.ts | 7 ++++--- src/util/typecheck.ts | 7 +++++++ src/world/World.ts | 12 +++++++++--- 10 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/Item.ts b/src/Item.ts index 3f87be1..5a5c42d 100644 --- a/src/Item.ts +++ b/src/Item.ts @@ -2,7 +2,7 @@ import ItemDef from "./defs/ItemDef.js" import BlockDef from "./defs/BlockDef.js" import { itemdefs, blockdefs } from "./main.js" import Block from "./block/Block.js" -import { type HasData, type BaseData, type Flatten } from "./util/interfaces.js" +import { type HasData, type BaseData, type Flatten, type NamespacedId } from "./util/interfaces.js" export default class Item implements HasData { @@ -13,7 +13,7 @@ export default class Item implements HasData { private readonly def: ItemDef | BlockDef - constructor(def: ItemDef | BlockDef | string) { + constructor(def: ItemDef | BlockDef | NamespacedId) { if (def instanceof ItemDef || def instanceof BlockDef) this.def = def else { let itemdef = itemdefs.get(def) || blockdefs.get(def) diff --git a/src/block/Block.ts b/src/block/Block.ts index 7e3fabe..aca3d9e 100644 --- a/src/block/Block.ts +++ b/src/block/Block.ts @@ -4,7 +4,7 @@ import Dim2 from "../dim/Dim2.js" import Dim3 from "../dim/Dim3.js" import { blockdefs, debug } from "../main.js" import Graphics from "../Graphics.js" -import { type HasData, type BaseData, type Flatten } from "../util/interfaces.js" +import { type HasData, type BaseData, type Flatten, type NamespacedId } from "../util/interfaces.js" import World from "../world/World.js" export default class Block implements HasData { @@ -18,7 +18,7 @@ export default class Block implements HasData { private readonly light: { sky: number, block: number } - constructor(def: BlockDef | string) { + constructor(def: BlockDef | NamespacedId) { if (def instanceof BlockDef) this.def = def else { let blockdef = blockdefs.get(def) diff --git a/src/block/ContainerBlock.ts b/src/block/ContainerBlock.ts index 2736f7f..d169722 100644 --- a/src/block/ContainerBlock.ts +++ b/src/block/ContainerBlock.ts @@ -1,13 +1,13 @@ import Inventory, { type InventoryData } from "../Inventory.js" import BlockDef from "../defs/BlockDef.js" -import { type Flatten, type HasInventory } from "../util/interfaces.js" +import { type NamespacedId, type Flatten, type HasInventory } from "../util/interfaces.js" import Block, { type BlockData } from "./Block.js" export default class ContainerBlock extends Block implements HasInventory { readonly inventory: Inventory - constructor(def: BlockDef | string, data: Partial = {}) { + constructor(def: BlockDef | NamespacedId, data: Partial = {}) { super(def) this.inventory = new Inventory(this.def.inventorySlots || 27, this.def.inventoryColumns || 9, data.items) } diff --git a/src/defs/Base.ts b/src/defs/Base.ts index f9ddf96..1ef72ed 100644 --- a/src/defs/Base.ts +++ b/src/defs/Base.ts @@ -1,5 +1,6 @@ import Texture from "../texture/Texture.js" import { getTexture } from "../main.js" +import { type NamespacedId } from "../util/interfaces.js"; export default class Base { readonly namespace: string; @@ -14,8 +15,8 @@ export default class Base { else this.texture = getTexture(this.assetsPath) } - get id() { - return this.namespace + ":" + this.idname + get id(): NamespacedId { + return `${this.namespace}:${this.idname}` } get assetsPath() { diff --git a/src/entity/Entity.ts b/src/entity/Entity.ts index 3bb56dc..89a2f60 100644 --- a/src/entity/Entity.ts +++ b/src/entity/Entity.ts @@ -5,7 +5,7 @@ import EntityDef from "../defs/EntityDef.js" import { entitydefs } from "../main.js" import World from "../world/World.js" import Graphics from "../Graphics.js" -import { type Flatten, type BaseData, type HasData } from "../util/interfaces.js" +import { type Flatten, type BaseData, type HasData, type NamespacedId } from "../util/interfaces.js" import AttributeList from "../AttributeList.js" export default class Entity implements HasData { @@ -36,7 +36,7 @@ export default class Entity implements HasData { onGround: boolean inFluid: boolean - constructor(def: EntityDef | string, spawnTime: number, data: Partial = {}) { + constructor(def: EntityDef | NamespacedId, spawnTime: number, data: Partial = {}) { if (def instanceof EntityDef) this.def = def else { let entitydef = entitydefs.get(def) diff --git a/src/entity/Player.ts b/src/entity/Player.ts index 829b0f9..8fdecb3 100644 --- a/src/entity/Player.ts +++ b/src/entity/Player.ts @@ -10,6 +10,8 @@ import Texture from "../texture/Texture.js" import World from "../world/World.js" import { type Flatten } from "../util/interfaces.js" +const playerDef = new PlayerDef() + export default class Player extends Entity { private name: string @@ -19,7 +21,7 @@ export default class Player extends Entity { readonly hotbar: Inventory constructor(skin: string, name: string, spawnTime: number, data: Partial = {}) { - super(new PlayerDef(), spawnTime, { ...data, position: [0, 1, 0] }) + super(playerDef, spawnTime, { ...data, position: [0, 1, 0] }) this.name = name this.hotbar = data.hotbar ? new Inventory(5, 5, data.hotbar) : new Inventory(5) this.skin = skin diff --git a/src/main.ts b/src/main.ts index 31bd37b..511db2f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,7 @@ import DebugScreen from "./util/DebugScreen.js" import CreativeInventory from "./CreativeInventory.js" import Entity, { type EntityData } from "./entity/Entity.js" import Item from "./Item.js" +import { type NamespacedId } from "./util/interfaces.js" console.log("Never Gonna Give You Up") @@ -108,9 +109,9 @@ function perfRun(name: "tick" | "draw", fn: Function, target: number) { } } -async function loadDefs(path: string, cls: any): Promise> { +async function loadDefs(path: string, cls: any): Promise> { let data = await YSON.load(path) - let defs = new Map() + let defs = new Map() let namespaces = Object.keys(data) for (let ns of namespaces) { @@ -417,7 +418,7 @@ export function getFirstFluid(world: World, x: number, y: number, startZ: number return { block: undefined, z: world.minZ } } -export function createBlock(id: string, data: Partial = {}) { +export function createBlock(id: NamespacedId, data: Partial = {}) { const blockdef = blockdefs.get(id) if (!blockdef) { console.trace() @@ -428,7 +429,7 @@ export function createBlock(id: string, data: P else return new Block(blockdef) } -export function createEntity(id: string, spawnTime: number, data: Partial = {}) { +export function createEntity(id: NamespacedId, spawnTime: number, data: Partial = {}) { data.id = id if (isItemEntityData(data, id)) return new ItemEntity(null, spawnTime, data) else return new Entity(id, spawnTime, data) diff --git a/src/util/interfaces.ts b/src/util/interfaces.ts index b20fd14..84f46f6 100644 --- a/src/util/interfaces.ts +++ b/src/util/interfaces.ts @@ -1,5 +1,4 @@ import Inventory from "../Inventory" -import ItemEntity from "../entity/ItemEntity" export type ArrayElement = A extends readonly (infer T)[] ? T : never @@ -12,11 +11,13 @@ export interface HasInventory { } export type BaseData = { - id: string + id: NamespacedId } export interface HasData { getData: (...args: any) => {} } -export type DataOf = ReturnType \ No newline at end of file +export type DataOf = ReturnType + +export type NamespacedId = `${string}:${string}` \ No newline at end of file diff --git a/src/util/typecheck.ts b/src/util/typecheck.ts index 6495375..7129ba9 100644 --- a/src/util/typecheck.ts +++ b/src/util/typecheck.ts @@ -1,3 +1,10 @@ +import { type NamespacedId } from "./interfaces"; + export function isInteger(x: unknown): x is number { return typeof x == "number" || Number.isInteger(x) } + +export const namespacedIdRegex = /^([a-z_]+):([a-z_]+)$/ +export function isNamespacedId(s: string): s is NamespacedId { + return namespacedIdRegex.test(s) +} \ No newline at end of file diff --git a/src/world/World.ts b/src/world/World.ts index d79d1ff..34e770f 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -5,6 +5,8 @@ import YSON from "https://j0code.github.io/browserjs-yson/main.mjs" import { getFirstBlock } from "../main.js" import Player, { type PlayerData } from "../entity/Player.js" import { createBlock, createEntity } from "../main.js" +import { type NamespacedId } from "../util/interfaces.js" +import { isNamespacedId } from "../util/typecheck.js" export default class World { @@ -30,7 +32,11 @@ export default class World { const semi = stringBlocks.indexOf(";") if (!semi) return // invalid save - const blockIds = stringBlocks.substring(0, semi).split(",") + const blockIdStrings: string[] = stringBlocks.substring(0, semi).split(",") + const blockIds: NamespacedId[] = blockIdStrings.map(v => { + if (isNamespacedId(v)) return v + else return "tiny:air" + }) const blocks = Array.from(stringBlocks.substring(semi + 1)).map(v => v.charCodeAt(0)) const blockDataMap: Map<`${number},${number},${number}`, BlockData> = new Map() @@ -105,7 +111,7 @@ export default class World { return Array.from(this.blocks.values()) } - setBlock(x: number, y: number, z: number, block: Block | string, data?: Partial) { + setBlock(x: number, y: number, z: number, block: Block | NamespacedId, data?: Partial) { const pos = this.validBlockPosition(x, y, z) if (!pos) return [x, y, z] = pos @@ -153,7 +159,7 @@ export default class World { return entities as E[] } - spawn(entity: Entity | string, data?: Partial) { + spawn(entity: Entity | NamespacedId, data?: Partial) { if (typeof entity == "string") { this.entities.add(createEntity(entity, this.tickCount, data)) } else {