import type { EcsType } from '../built-in-types/EcsType' import { ByteBuffer, createByteBuffer } from '../serialization/ByteBuffer' import { Entity } from './entity' import { deepReadonly, DeepReadonly } from './utils' /** * @public */ export type EcsResult = T extends EcsType ? ReturnType : never /** * @public */ export type ComponentType = EcsResult /** * @public */ export type ComponentDefinition> = { _id: number has(entity: Entity): boolean getFrom(entity: Entity): DeepReadonly> getOrNull(entity: Entity): DeepReadonly> | null // adds this component to the list "to be reviewed next frame" create(entity: Entity, val?: ComponentType): ComponentType // adds this component to the list "to be reviewed next frame" mutable(entity: Entity): ComponentType createOrReplace(entity: Entity, val?: ComponentType): ComponentType deleteFrom(entity: Entity): ComponentType | null upsertFromBinary(entity: Entity, data: ByteBuffer): ComponentType | null updateFromBinary(entity: Entity, data: ByteBuffer): ComponentType | null // allocates a buffer and returns new buffer toBinary(entity: Entity): ByteBuffer // writes to a pre-allocated buffer writeToByteBuffer(entity: Entity, buffer: ByteBuffer): void iterator(): Iterable<[Entity, ComponentType]> // Dirty dirtyIterator(): Iterable clearDirty(): void isDirty(entity: Entity): boolean } export function defineComponent( componentId: number, spec: T // meta: { syncFlags } ): ComponentDefinition { const data = new Map>() const dirtyIterator = new Set() return { _id: componentId, isDirty: function (entity: Entity): boolean { return dirtyIterator.has(entity) }, has: function (entity: Entity): boolean { return data.has(entity) }, deleteFrom: function (entity: Entity): ComponentType | null { const component = data.get(entity) data.delete(entity) dirtyIterator.add(entity) return component || null }, getOrNull: function ( entity: Entity ): DeepReadonly> | null { const component = data.get(entity) return component ? deepReadonly(component) : null }, getFrom: function (entity: Entity): DeepReadonly> { const component = data.get(entity) if (!component) { throw new Error( `[getFrom] Component ${componentId} for ${entity} not found` ) } return deepReadonly(component) }, create: function ( entity: Entity, value?: ComponentType ): ComponentType { const component = data.get(entity) if (component) { throw new Error( `[create] Component ${componentId} for ${entity} already exists` ) } const usedValue = value === undefined ? spec.create() : value data.set(entity, usedValue) dirtyIterator.add(entity) return usedValue }, createOrReplace: function ( entity: Entity, value?: ComponentType ): ComponentType { const usedValue = value === undefined ? spec.create() : value data.set(entity, usedValue!) dirtyIterator.add(entity) return usedValue! }, mutable: function (entity: Entity): ComponentType { const component = data.get(entity) if (!component) { throw new Error( `[mutable] Component ${componentId} for ${entity} not found` ) } dirtyIterator.add(entity) return component }, iterator: function* (): Iterable<[Entity, ComponentType]> { for (const [entity, component] of data) { yield [entity, component] } }, dirtyIterator: function* (): Iterable { for (const entity of dirtyIterator) { yield entity } }, toBinary(entity: Entity): ByteBuffer { const component = data.get(entity) if (!component) { throw new Error( `[toBinary] Component ${componentId} for ${entity} not found` ) } const writeBuffer = createByteBuffer() spec.serialize(component, writeBuffer) return writeBuffer }, writeToByteBuffer(entity: Entity, buffer: ByteBuffer): void { const component = data.get(entity) if (!component) { throw new Error( `[writeToByteBuffer] Component ${componentId} for ${entity} not found` ) } spec.serialize(component, buffer) }, updateFromBinary( entity: Entity, buffer: ByteBuffer ): ComponentType | null { const component = data.get(entity) if (!component) { throw new Error( `[updateFromBinary] Component ${componentId} for ${entity} not found` ) } return this.upsertFromBinary(entity, buffer) }, upsertFromBinary( entity: Entity, buffer: ByteBuffer ): ComponentType | null { const newValue = spec.deserialize(buffer) data.set(entity, newValue) dirtyIterator.add(entity) return newValue }, clearDirty: function () { dirtyIterator.clear() } } }