{"version":3,"sources":["../src/component.ts","../src/decorators.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Entity } from './entity';\nimport { pendingFields, pendingInitializers } from './decorators';\n\nexport type ComponentId = number;\nexport type ComponentName = string;\nexport type ComponentField = {\n    fieldName: string;\n    defaultValue?: unknown;\n};\n\n/**\n * Component that can be attached to entities.\n */\nexport abstract class Component {\n    /**\n     * Bitflag id assigned to a component class at registration time.\n     * Keyed by the class reference itself, not by class name, so the ECS\n     * survives identifier mangling by minifiers and obfuscators.\n     */\n    static ComponentIdMap: Map<ComponentConstructor, ComponentId> = new Map();\n\n    static ComponentFieldMap: Map<\n        ComponentConstructor,\n        Map<string, ComponentField>\n    > = new Map();\n\n    static ComponentFieldInitializeMap: Map<\n        ComponentConstructor,\n        Map<string, Array<string>>\n    > = new Map();\n\n    static maxId = 0;\n\n    declare componentId: ComponentId;\n\n    private declare entity: Entity;\n\n    setValues(values: Record<string, any>): void {\n        const ctor = this.constructor as ComponentConstructor;\n        const fields = Component.ComponentFieldMap.get(ctor);\n        const initializers = Component.ComponentFieldInitializeMap.get(ctor);\n\n        if (!fields) {\n            return;\n        }\n\n        fields.forEach(({ fieldName }) => {\n            if (typeof values[fieldName] === 'undefined') {\n                throw new Error(\n                    `Value not provided for ${fieldName} on component ${this.constructor.name}.`\n                );\n            } else {\n                (this as any)[fieldName] = values[fieldName];\n\n                if (initializers && initializers.has(fieldName)) {\n                    initializers.get(fieldName)?.forEach((otherField) => {\n                        (this as any)[otherField] = values[fieldName];\n                    });\n                }\n            }\n        });\n    }\n    setEntity(entity: Entity): void {\n        this.entity = entity;\n    }\n\n    getEntity(): Entity {\n        if (!this.entity) {\n            throw new Error(\n                \"Component tried to fetch it's entity, but none was found\"\n            );\n        }\n        return this.entity;\n    }\n\n    onComponentRemoved() {\n        // no-op\n    }\n\n    toString(): string {\n        return `${this.constructor.name}`;\n    }\n}\n\ntype NonFunctionPropertyNames<T> = {\n    // eslint-disable-next-line @typescript-eslint/ban-types\n    [K in keyof T]: T[K] extends Function ? never : K;\n}[keyof T];\n\nexport type ComponentArgs<C> = {\n    [Property in Exclude<\n        NonFunctionPropertyNames<C>,\n        keyof Component\n    >]: C[Property];\n};\nexport type ComponentConstructor = { new (...args: any[]): Component };\n\nexport const registerComponentWithSpecificId = <T extends ComponentConstructor>(\n    constructor: T,\n    id: number\n) => {\n    registerComponent(constructor, id);\n};\n\nexport function RegisterComponent<T extends ComponentConstructor>(\n    value: T,\n    _context: ClassDecoratorContext\n): void {\n    Component.maxId++;\n    const newComponentId = Component.maxId;\n    registerComponent(value, newComponentId);\n\n    const fields = pendingFields.splice(0);\n    const fieldMap = Component.ComponentFieldMap.get(value);\n    if (fieldMap) {\n        for (const f of fields) {\n            fieldMap.set(f.fieldName, f);\n        }\n    }\n\n    const initializers = pendingInitializers.splice(0);\n    if (initializers.length > 0) {\n        let initMap = Component.ComponentFieldInitializeMap.get(value);\n        if (!initMap) {\n            initMap = new Map();\n            Component.ComponentFieldInitializeMap.set(value, initMap);\n        }\n        for (const init of initializers) {\n            if (!initMap.has(init.initFrom)) {\n                initMap.set(init.initFrom, []);\n            }\n            initMap.get(init.initFrom)?.push(init.field);\n        }\n    }\n}\n\nconst registerComponent = (\n    constructor: ComponentConstructor,\n    newComponentId: number\n) => {\n    constructor.prototype.componentId = newComponentId;\n    Component.ComponentIdMap.set(constructor, newComponentId);\n\n    if (!Component.ComponentFieldMap.has(constructor)) {\n        Component.ComponentFieldMap.set(constructor, new Map());\n    }\n};\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Component, ComponentConstructor, ComponentField } from './component';\nimport { Entity } from './entity';\n\n/**\n * Pending fields collected by member decorators, consumed by @RegisterComponent.\n * Safe because decorator evaluation is synchronous per class.\n */\nexport const pendingFields: ComponentField[] = [];\nexport const pendingInitializers: Array<{\n    initFrom: string;\n    field: string;\n}> = [];\n\n/**\n * Register field\n */\nexport function field(\n    _value: undefined,\n    context: ClassFieldDecoratorContext\n): void {\n    pendingFields.push({ fieldName: String(context.name) });\n}\n\nexport const validate = <T>(\n    validateFunction: (val: T) => boolean\n) => {\n    return function (\n        value: ClassAccessorDecoratorTarget<Component, T>,\n        context: ClassAccessorDecoratorContext<Component, T>\n    ): ClassAccessorDecoratorResult<Component, T> {\n        pendingFields.push({ fieldName: String(context.name) });\n        return {\n            set(this: Component, val: T) {\n                if (validateFunction(val)) {\n                    value.set.call(this, val);\n                } else {\n                    throw new Error('Component failed validation function.');\n                }\n            }\n        };\n    };\n};\n\n/**\n * Initialize field as another field on startup.\n * Useful for patterns where you have a field that derives from another.\n * (Example: A 'current' hp value might be initialized from a 'max' hp value.\n * @param initAsField\n */\nexport function initializeAs(initAsField: string) {\n    return function (_value: undefined, context: ClassFieldDecoratorContext) {\n        pendingInitializers.push({\n            initFrom: initAsField,\n            field: String(context.name)\n        });\n    };\n}\n\nexport function init<C>(\n    initValue: C\n): (\n    _value: undefined,\n    context: ClassFieldDecoratorContext\n) => (initialValue: C) => C {\n    return function (\n        _value: undefined,\n        context: ClassFieldDecoratorContext\n    ): (initialValue: C) => C {\n        pendingFields.push({\n            fieldName: String(context.name),\n            defaultValue: initValue\n        });\n        return () => initValue;\n    };\n}\n\nexport function children(\n    value: ClassAccessorDecoratorTarget<Component, Set<Entity>>,\n    context: ClassAccessorDecoratorContext<Component, Set<Entity>>\n): ClassAccessorDecoratorResult<Component, Set<Entity>> {\n    pendingFields.push({ fieldName: String(context.name) });\n\n    return {\n        set(this: Component, childrenVal: Set<Entity>) {\n            const addCleanupCallback = (entity: Entity) => {\n                entity.addCleanupCallback(() => {\n                    extendedSet.delete(entity);\n                });\n            };\n            class ExtendedSet extends Set<Entity> {\n                public add(val: Entity) {\n                    if (super.has(val)) {\n                        return this;\n                    }\n                    super.add(val);\n                    addCleanupCallback(val);\n                    return this;\n                }\n            }\n\n            const extendedSet = new ExtendedSet(childrenVal);\n            value.set.call(this, extendedSet as Set<Entity>);\n        }\n    };\n}\n\n/**\n * Mark entity property as parent\n * @param parentComponentClass Component which will get updated refs to any child entity\n * @param aggregatePropertyKey Property name of array which will contain all child refs\n * @returns Decorator\n */\nexport function parent<\n    V extends { new (): Component & Record<U, Set<Entity>> },\n    U extends string\n>(parentComponentClass: V, aggregatePropertyKey: U) {\n    return (\n        value: ClassAccessorDecoratorTarget<Component, Entity>,\n        context: ClassAccessorDecoratorContext<Component, Entity>\n    ): ClassAccessorDecoratorResult<Component, Entity> => {\n        const propertyKey = String(context.name);\n        pendingFields.push({ fieldName: propertyKey });\n\n        return {\n            set(this: Component, newParent: Entity) {\n                const thisEntity = this.getEntity();\n\n                // has old parent?\n                const currentParent = value.get.call(this);\n                if (currentParent) {\n                    if (currentParent.has(parentComponentClass)) {\n                        const oldParentAggregateProp =\n                            currentParent.get(parentComponentClass)[\n                                aggregatePropertyKey\n                            ];\n                        oldParentAggregateProp.delete(thisEntity);\n                        if (oldParentAggregateProp.size === 0) {\n                            currentParent.remove(parentComponentClass);\n                        }\n                    }\n                }\n\n                value.set.call(this, newParent);\n\n                // Adding entity reference to parent component class\n                if (!newParent.has(parentComponentClass)) {\n                    const childrenSet = new Set<Entity>();\n                    childrenSet.add(thisEntity);\n                    newParent.addComponent(parentComponentClass, {\n                        [aggregatePropertyKey]: childrenSet\n                    } as any);\n                } else {\n                    const childrenSet = newParent.get(parentComponentClass)[\n                        aggregatePropertyKey\n                    ] as Set<Entity> | undefined;\n\n                    if (!childrenSet) {\n                        const newChildrenSet = new Set<Entity>();\n                        newChildrenSet.add(thisEntity);\n\n                        (newParent.get(parentComponentClass)[\n                            aggregatePropertyKey\n                        ] as Set<Entity>) = newChildrenSet;\n                    } else {\n                        childrenSet.add(thisEntity);\n                    }\n                }\n\n                // cleans up this reference if component is removed\n                const componentClass = this\n                    .constructor as ComponentConstructor;\n                newParent.addCleanupCallback(() => {\n                    if (thisEntity.has(this)) {\n                        // entity property has been removed from component, we remove the component\n                        thisEntity.removeComponent(componentClass);\n                    }\n                });\n            }\n        };\n    };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,IAAM,gBAAkC,CAAC;AACzC,IAAM,sBAGR,CAAC;;;ADEC,IAAe,aAAf,MAAe,WAAU;AAAA,EAwB5B,UAAU,QAAmC;AACzC,UAAM,OAAO,KAAK;AAClB,UAAM,SAAS,WAAU,kBAAkB,IAAI,IAAI;AACnD,UAAM,eAAe,WAAU,4BAA4B,IAAI,IAAI;AAEnE,QAAI,CAAC,QAAQ;AACT;AAAA,IACJ;AAEA,WAAO,QAAQ,CAAC,EAAE,UAAU,MAAM;AAC9B,UAAI,OAAO,OAAO,SAAS,MAAM,aAAa;AAC1C,cAAM,IAAI;AAAA,UACN,0BAA0B,SAAS,iBAAiB,KAAK,YAAY,IAAI;AAAA,QAC7E;AAAA,MACJ,OAAO;AACH,QAAC,KAAa,SAAS,IAAI,OAAO,SAAS;AAE3C,YAAI,gBAAgB,aAAa,IAAI,SAAS,GAAG;AAC7C,uBAAa,IAAI,SAAS,GAAG,QAAQ,CAAC,eAAe;AACjD,YAAC,KAAa,UAAU,IAAI,OAAO,SAAS;AAAA,UAChD,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,UAAU,QAAsB;AAC5B,SAAK,SAAS;AAAA,EAClB;AAAA,EAEA,YAAoB;AAChB,QAAI,CAAC,KAAK,QAAQ;AACd,YAAM,IAAI;AAAA,QACN;AAAA,MACJ;AAAA,IACJ;AACA,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,qBAAqB;AAAA,EAErB;AAAA,EAEA,WAAmB;AACf,WAAO,GAAG,KAAK,YAAY,IAAI;AAAA,EACnC;AACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AArEsB,WAMX,iBAAyD,oBAAI,IAAI;AANtD,WAQX,oBAGH,oBAAI,IAAI;AAXM,WAaX,8BAGH,oBAAI,IAAI;AAhBM,WAkBX,QAAQ;AAlBZ,IAAe,YAAf;AAoFA,IAAM,kCAAkC,CAC3C,aACA,OACC;AACD,oBAAkB,aAAa,EAAE;AACrC;AAEO,SAAS,kBACZ,OACA,UACI;AACJ,YAAU;AACV,QAAM,iBAAiB,UAAU;AACjC,oBAAkB,OAAO,cAAc;AAEvC,QAAM,SAAS,cAAc,OAAO,CAAC;AACrC,QAAM,WAAW,UAAU,kBAAkB,IAAI,KAAK;AACtD,MAAI,UAAU;AACV,eAAW,KAAK,QAAQ;AACpB,eAAS,IAAI,EAAE,WAAW,CAAC;AAAA,IAC/B;AAAA,EACJ;AAEA,QAAM,eAAe,oBAAoB,OAAO,CAAC;AACjD,MAAI,aAAa,SAAS,GAAG;AACzB,QAAI,UAAU,UAAU,4BAA4B,IAAI,KAAK;AAC7D,QAAI,CAAC,SAAS;AACV,gBAAU,oBAAI,IAAI;AAClB,gBAAU,4BAA4B,IAAI,OAAO,OAAO;AAAA,IAC5D;AACA,eAAW,QAAQ,cAAc;AAC7B,UAAI,CAAC,QAAQ,IAAI,KAAK,QAAQ,GAAG;AAC7B,gBAAQ,IAAI,KAAK,UAAU,CAAC,CAAC;AAAA,MACjC;AACA,cAAQ,IAAI,KAAK,QAAQ,GAAG,KAAK,KAAK,KAAK;AAAA,IAC/C;AAAA,EACJ;AACJ;AAEA,IAAM,oBAAoB,CACtB,aACA,mBACC;AACD,cAAY,UAAU,cAAc;AACpC,YAAU,eAAe,IAAI,aAAa,cAAc;AAExD,MAAI,CAAC,UAAU,kBAAkB,IAAI,WAAW,GAAG;AAC/C,cAAU,kBAAkB,IAAI,aAAa,oBAAI,IAAI,CAAC;AAAA,EAC1D;AACJ;","names":[]}