import { ActorModel, } from '../models/ActorModel'; import { addTag, } from '../../tags/addTag'; import { Epistemology, } from '../epistemology/Epistemology'; import { IEpistemology, } from '../epistemology/IEpistemology'; import { findAllGenerate, } from '../querying/findAllGenerate'; import { FindModelArgs, } from '../querying/FindModelArgs'; import { getStructuredTags, } from '../../tags/getStructuredTags'; import { getTag, } from '../../tags/getTag'; import { IModel, } from '../models/IModel'; import { IModelConstructorArgs, } from '../models/IModelConstructorArgs'; import { IOntology, } from '../ontology/IOntology'; import { ISerializedWorld, } from './ISerializedWorld'; import { ITag, } from '../../tags/ITag'; import { IWorld, } from './IWorld'; import { LocationModel, } from '../models/LocationModel'; import { ModelType, } from '../models/ModelType'; import { ObjectModel, } from '../models/ObjectModel'; import { OnticTypes, } from '../ontology/OnticTypes'; import { PortalModel, } from '../models/PortalModel'; import { removeTag, } from '../../tags/removeTag'; import { Tag, } from '../../tags/Tag'; import { ThoughtModel, } from '../models/ThoughtModel'; import { assert, assertValid, } from 'ts-assertions'; import { TypedModelInterfaces, } from '../models/TypedModelInterfaces'; import { WorldType, } from './WorldType'; export class World implements IWorld { public get being() { return null; } private readonly __knowledge: IEpistemology< ModelType.Thought, ModelType > = new Epistemology(this, { modelType: ModelType.Thought }); public get knowledge() { return this.__knowledge; } private __models: Readonly< Record> > = Object.freeze({}); public get models() { return this.__models; } private readonly __name: string; public get name() { return this.__name; } private __tags: readonly ITag[] = Object.freeze([]); public get tags() { return this.__tags; } private readonly __type = WorldType; public get type() { return this.__type; } public readonly addTag = (tag: string | ITag) => ( void (this.__tags = Object.freeze(addTag(tag, this.tags))) ); public readonly removeTag = (tag: string | ITag) => ( void (this.__tags = Object.freeze(removeTag(this.tags, tag))) ); constructor( name: string, models?: Record< string, IModel >, initialize?: (self: IWorld) => void, finalize?: (self: IWorld) => void, tags?: Tag[] | readonly Tag[], ) { this.__name = assertValid(name); if (models && typeof models === 'object') { this.__models = models; } if (typeof initialize === 'function') { this.initialize = initialize; } if (typeof finalize === 'function') { this.finalize = finalize; } if (Array.isArray(tags)) { this.__tags = getStructuredTags(tags); } } public readonly addModel = < Type extends ModelType, Being extends OnticTypes = any, Knowledge extends ModelType = any, >( modelArgs: IModelConstructorArgs< Type, Being, Knowledge >, ctor?: new ( world: IWorld, args: IModelConstructorArgs, ) => IModel, ): ( Type extends keyof TypedModelInterfaces ? TypedModelInterfaces[Type] : IModel ) => { this.validateModelArgs(modelArgs); let temp: IModel; if (typeof ctor === 'function') { temp = new ctor(this, modelArgs); } else if (modelArgs.type === ModelType.Actor) { temp = new ActorModel(this, { ...modelArgs, being: modelArgs.being as IOntology, knowledge: modelArgs.knowledge as IEpistemology< ModelType.Actor, Knowledge >, type: modelArgs.type as ModelType.Actor, }); } else if (modelArgs.type === ModelType.Location) { temp = new LocationModel(this, { ...modelArgs, being: modelArgs.being as IOntology, knowledge: null, type: modelArgs.type as ModelType.Location, }) as any; } else if (modelArgs.type === ModelType.Object) { temp = new ObjectModel(this, { ...modelArgs, being: modelArgs.being as IOntology, knowledge: null, type: modelArgs.type as ModelType.Object, }); } else if (modelArgs.type === ModelType.Portal) { temp = new PortalModel(this, { ...modelArgs, being: modelArgs.being as IOntology, knowledge: null, type: modelArgs.type as ModelType.Portal, }); } else if (modelArgs.type === ModelType.Thought) { temp = new ThoughtModel(this, { ...modelArgs, being: null, knowledge: null, type: modelArgs.type as ModelType.Thought, }); } else { throw new Error('Type argument not recognized in World.addModel.'); } this.__models = Object.freeze({ ...this.models, [modelArgs.name]: temp, }); return temp as ( Type extends keyof TypedModelInterfaces ? TypedModelInterfaces[Type] : IModel ); }; public readonly getTag = (toSearch: string | ITag) => ( getTag(this.tags, toSearch) ); public readonly initialize = (self: IWorld) => { if (self.knowledge && typeof self.knowledge.initialize === 'function') { self.knowledge.initialize(self.knowledge); } this.children().forEach((child) => child.initialize(child as any)); }; public readonly finalize = (self: IWorld) => { if (self.knowledge && typeof self.knowledge.finalize === 'function') { self.knowledge.finalize(self.knowledge); } this.children().forEach((child) => child.finalize(child as any)); }; public readonly removeModel = ( model: string | IModel, ) => { const copy = { ...this.__models }; if (typeof model === 'string') { delete copy[model]; } else { delete copy[model.name]; } this.__models = Object.freeze(copy); }; public readonly children = () => Object.values(this.models); public readonly clone = (name: string): IWorld => { const world: IWorld = new World(name); this.descendants().forEach((model) => world.addModel({ ...model })); return world; }; public readonly descendants = () => this.findAll('*'); public readonly destroy = () => { this.finalize(this); this.descendants().forEach((desc) => { desc.destroy(desc as any); this.removeModel(desc); }); this.tags.forEach(this.removeTag); ((self: any) => { delete self.__being; delete self.being; delete self.__knowledge; delete self.knowledge; delete self.__models; delete self.models; delete self.__tags; delete self.tags; delete self.__type; delete self.type; })(this); }; public readonly find = < Type extends ModelType, Being extends OnticTypes, Knowledge extends ModelType, >( args: string | FindModelArgs, ) => this.findAllGenerator( typeof args === 'string' ? { name: args } : args, ).next().value || null; public readonly findAll = < Type extends ModelType, Being extends OnticTypes, Knowledge extends ModelType, >( args: '*' | FindModelArgs, ): readonly IModel[] => { const ret: IModel[] = []; const gen = this.findAllGenerator(args); while (true) { const { done, value, } = gen.next(); if (done || !value) { break; } if (!value) { break; } ret.push(value as IModel); } return Object.freeze(ret); }; public readonly findAllGenerator = ((self: IWorld) => ( function* (args: '*' | FindModelArgs) { assert(args, 'The args argument to World.findAllGenerator was not valid.'); yield* findAllGenerate(Object.values(self.models), args); } ))(this); public readonly validateModelArgs = < Type extends ModelType, Being extends OnticTypes, >(args: any): args is IModelConstructorArgs => { assert(args, 'There were no arguments passed to World.addModel.'); assert( args.name, 'There was no name property in the argument array passed to ' + 'World.addModel.', ); assert( !(args.name in this.models), 'There was already a model with the provided name in the models map.', ) assert( args.type, 'There was no type property in the arguments array passed to ' + 'World.addModel.', ); return true; }; public readonly serialize = ( self: IWorld, spaces: number = 0, ) => JSON.stringify(self.serializeToObject(self), null, spaces); public readonly serializeToObject = ( self: IWorld, ): ISerializedWorld => ({ knowledge: self.knowledge.serializeToObject(self.knowledge), name: self.name, models: Object.values(self.models).map((model) => model.serializeToObject(model)), tags: [ ...self.tags ], }); }