// Lib is its own class because, while it would make sense to group its functions and macros in Archive.kiss, // other files would not be able to (load "Archive.kiss") for the macro definitions without taking on Archive's constructor. // External programs can load Lib.kiss with (loadFrom "nat-archive-tool" "src/nat/Lib.kiss") (defMacro hasComponent [e componentType] `(.exists .components ,e ,(symbolName componentType))) (defMacro _componentPath [archive e componentType] `(joinPath .archiveDir (the nat.Archive ,archive) "components" (+ (dictGet (the Map .components ,e) ,(symbolName componentType)) ".json"))) // Changes to the object returned by (readComponent) will not be saved! Use (withWritableComponents) for making changes (defMacro readComponent [archive e componentType] // TODO add to the documentation a hint that macros should use fully qualified type paths so macro caller classes don't need to import everything `(the nat.components ,componentType (tink.Json.parse (sys.io.File.getContent (_componentPath ,archive ,e ,componentType))))) // Components have to be saved individually after writing because the Entity only knows their ids, // not the data they contain. This is more ergonomically done by using (withWritableComponents...) (defMacro writeComponent [archive e componentType c] `(sys.io.File.saveContent (_componentPath ,archive ,e ,componentType) (tink.Json.stringify (the nat.components ,componentType ,c)))) // TODO check not overwriting a component (defMacro addComponent [archive e componentType c] `(withWritableEntry ,archive ,e (let [componentId (uuid.Uuid.v4)] (dictSet .components ,e ,(symbolName componentType) componentId) (writeComponent ,archive ,e ,componentType ,c) ,e))) // Retrieve multiple components from an Entity with mutable access. // All components will be serialized after the block is done. (defMacro withWritableComponents [archive e bindings &body body] (let [bindingPairs (groups (expList bindings) 2 Throw) bindingList [] saveList [] retValSymbol (symbol)] (doFor [name type] bindingPairs (bindingList.push `&mut ,name) (bindingList.push `(readComponent ,archive ,e ,type)) (saveList.push `(writeComponent ,archive ,e ,type ,name))) `(let [,@bindingList ,retValSymbol {,@body}] ,@saveList (.refreshEntry ,archive ,e) // Check the entry in and out of systems when its components change ,retValSymbol))) (defMacro withWritableEntry [archive e &body body] (let [retValSymbol (symbol)] `(let [,retValSymbol {,@body}] (.refreshEntry ,archive ,e) ,retValSymbol))) // Create a system that selects Entries according to a single string component (i.e. Name or Author) matching the given value (defMacro stringComponentSystem [archive componentType value process] `(new System (lambda [archive :nat.Entry e] ?(and (hasComponent e ,componentType) (= ,value (readComponent ,archive e ,componentType)))) ,process)) (function tagList [archive e] (let [t (readComponent archive e Tags)] (collect (t.keys)))) (function tagsMatch [archive e tagsBoolExp] (BoolExpInterp.eval tagsBoolExp (tagList archive e))) (function componentsMatch [:nat.Entry e componentsBoolExp] (BoolExpInterp.eval componentsBoolExp (for =>cType cId e.components cType))) (function addFiles [:nat.Archive archive :nat.Entry e :Array files &opt leaveOriginalCopy] (withWritableEntry archive e (doFor file files (let [pathWithoutDir (haxe.io.Path.withoutDirectory file)] (unless !(= -1 (e.files.indexOf pathWithoutDir)) ((if leaveOriginalCopy sys.io.File.copy sys.FileSystem.rename) file (joinPath archive.archiveDir "files" pathWithoutDir)) (e.files.push pathWithoutDir)))))) (function addTags [:nat.Archive archive :nat.Entry e :Array tagsToAdd] (if (hasComponent e Tags) (withWritableComponents archive e [tags Tags] (doFor tag tagsToAdd (dictSet tags tag 1))) (addComponent archive e Tags (for tag tagsToAdd =>tag 1)))) (function removeTags [:nat.Archive archive :nat.Entry e :Array tagsToRemove] (when (hasComponent e Tags) (withWritableComponents archive e [tags Tags] (doFor tag tagsToRemove (tags.remove tag)))))