{"id":16484947,"url":"https://github.com/jobergner/backent-cli","last_synced_at":"2026-06-10T12:31:11.595Z","repository":{"id":43934191,"uuid":"307408704","full_name":"jobergner/backent-cli","owner":"jobergner","description":"backent-cli provides a toolkit to generate a server and a custom API as package which broadcasts state changes of entities automatically.","archived":false,"fork":false,"pushed_at":"2025-12-23T08:28:50.000Z","size":3320,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-14T15:12:57.904Z","etag":null,"topics":["game-development","go","golang","server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jobergner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-10-26T14:57:54.000Z","updated_at":"2025-12-23T08:28:54.000Z","dependencies_parsed_at":"2024-06-21T02:26:56.910Z","dependency_job_id":null,"html_url":"https://github.com/jobergner/backent-cli","commit_stats":null,"previous_names":["java-jonas/bar-cli"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jobergner/backent-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jobergner%2Fbackent-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jobergner%2Fbackent-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jobergner%2Fbackent-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jobergner%2Fbackent-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jobergner","download_url":"https://codeload.github.com/jobergner/backent-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jobergner%2Fbackent-cli/sbom","scorecard":{"id":526058,"data":{"date":"2025-08-11","repo":{"name":"github.com/jobergner/backent-cli","commit":"755c21686326e0335d36784719f80b138952f535"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"12 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T04:24:31.480Z","repository_id":43934191,"created_at":"2025-08-20T04:24:31.480Z","updated_at":"2025-08-20T04:24:31.480Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34153482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["game-development","go","golang","server"],"created_at":"2024-10-11T13:18:48.254Z","updated_at":"2026-06-10T12:31:11.568Z","avatar_url":"https://github.com/jobergner.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg width=\"260\"  src=\"./assets/gopher.png\"\u003e\n\u003c/p\u003e\n\n# backent-cli\nbackent-cli provides a toolkit to generate a server and a custom API as package which broadcasts state changes of entities automatically.\n\n``````\ngocloc . --not-match-d='node_modules|tmp' --not-match='stringified|easyjson|pregenerated'\n``````\n\n\n## Installation\n```\ngo get -u github.com/jobergner/backent-cli\n```\n\n# A quick Example:\n### Example `config.json`\nThe API is generated based on a configuration which may look like this:\n```JSON\n{\n    \"state\": {\n        \"player\": {\n            \"name\": \"string\",\n            \"items\": \"[]item\"\n        },\n        \"item\": {\n            \"name\": \"string\",\n        }\n    },\n    \"actions\": {\n        \"createPlayer\": {\n            \"name\": \"string\",\n            \"firstItemName\": \"string\"\n        }\n    }\n}\n```\n### Generate Server and API with your `config.json`\n```bash\nbackent-cli -config=config.json -out=backent/ generate \n```\n### Use the custom-generated engine API to broadcast all changes automatically\n```golang\nfunc CreatePlayer(params state.ReceivedParams, engine *state.Engine) {\n\n\tplayer := engine.CreatePlayer()                  // creating the player\n\n\tplayer.SetName(params.Name)                      // setting the player name\n\n\tplayer.AddItem().SetName(params.FirstItemName)   // add an item and set item name\n\n}\n```\n\n# Jump right into Experimenting!\nExplore backent-cli and its features with the Inspector and toy around until you feel comfortable. You may also want to explore the generated code itself!\n```bash\n# set up directories\nmkdir backent_example; cd backent_example;\ngo mod init backentexample;\n\n# generate the code\nbackent-cli -example -out=./backent/ generate;\n\n# run the server\ngo run .;\n# in a different window run the inspector, then open http://localhost:3100/:\nbackent-cli inspect;\n```\nThe Inspector is a graphical user interface for you to run locally and inspect your backent-cli generated server's behaviour, or in this case an example server that backent-cli will set up for you.\n\nYou can also inspect backent servers that are not running on port 3496 by using query parameters in the URL: `http://localhost:3100?port=8080`\n\n![alt text](./assets/inspector.png)\n\n# Overview\nWhen generating a server the console will output the content of a possible `main.go` file for you to copy and paste. You may then edit the actions and sideEffects.\n```golang\npackage main\n\n// import the API\nimport (\n    state \"yourproject/generatedpackage\"\n)\n\n// how many processing frames per second the server will run\nconst fps = 30\n\nfunc main() {\n\terr := state.Start(actions, sideEffects, fps, 3496)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// define what is being executed on receiving a message\nvar actions = state.Actions{\n\tCreatePlayer: func(params state.CreatePlayerParams, engine *state.Engine) {\n\t\tplayer := engine.CreatePlayer()                // creating the player\n\n\t\tplayer.SetName(params.Name)                    // setting the player name\n\n\t\tplayer.AddItem().SetName(params.FirstItemName) // add an item and set item name\n\n\t}, // state change is automatically broadcasted\n}\n\n// define what is being executed on server deploy and after all actions for a processing frame tick are processed\nvar sideEffects = state.SideEffects{\n\tOnDeploy:    func(engine *state.Engine) {},\n\tOnFrameTick: func(engine *state.Engine) {},\n}\n```\n## Connecting to the websocket endpoint may look like this:\n```javascript\nconst ws = new WebSocket(\"ws://localhost:3496/ws\");\nws.open = () =\u003e this.setSocketStatus(\"open\");\nws.onclose = () =\u003e this.setSocketStatus(\"closed\");\n\nws.onmessage = (e) =\u003e {\n    const message = JSON.parse(e.data);\n    if (message.kind === \"currentState\") {\n        console.log(\"receiving initial state:\", JSON.parse(message.content))\n    } else if (message.kind === \"update\") {\n        console.log(\"receiving update\", JSON.parse(message.content));\n    } else {\n        this.setReceivedData(\"received message response\", JSON.parse(message.content));\n    }\n};\n```\n## Send a Message to trigger an Action:\nThis is an example message the server understands via the `/ws` websocket connection. It interprets the message to trigger actions. In this case the server will trigger the `CreatePlayer` action with the given data passed as parameter.\n```JSON\n{\n    \"kind\": \"createPlayer\",\n    \"content\": \"{\\\"name\\\": \\\"string\\\",\\\"firstItemName\\\": \\\"string\\\"}\"\n}\n```\n## Server Endpoints\n| Endpoint   | Description                                                                                                                                                                         |\n| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `/ws`      | The Websocket endpoint. This is how a client can connect to the server. They will receive the current state of all entities when they connect, and from there all occuring updates. |\n| `/inspect` | Here any client can inspect the config the server was generated with. This can be helpful as it explains all types, actions and responses.                                          |\n| `/state`   | This endpoint returns the current state of all entities.                                                                                                                            |\n\n## CLI Flags\n| generate flags                 | Description                                                                                                            |\n| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |\n| `-out=\u003cstring\u003e`                | Which directory backent-cli is supposed to generate the code into. If the directory does not exist it will be created. |\n| `-config=\u003cstring\u003e`             | The config file which is used to generate the API                                                                      |\n| `-example=\u003coptional bool\u003e`     | With this flag enabled an example server will be generated and the `-config` flag will be ignored.                     |\n| `-engine_only=\u003coptional bool\u003e` | Enable to only generate the engine and API part of the package, omitting the server.                                   |\n\n| inspect flags    | Description                                                |\n| ---------------- | ---------------------------------------------------------- |\n| `-port=\u003cstring\u003e` | On which port the inspector should run (defaults to 3100). |\n\n\n# The Basics\n## Defining the Config:\nThe config's syntax is inspired by Go's own syntax. If you have knowledge of Go you will intuitively understand what is going on. And if you find yourself struggling and make mistakes, comprehensive error messages will help you correct them. There are however some additional restrictions to which values you can use where. More info on that here.\n\nThe config may consist of 3 parts: `state`, `actions` and `responses`.\n\n### state:\nThe state consists of types which you can consider the equivalent to Go's structs: Structures with field names and values describing the types. As it is with go, when defining a type, you can use it as a field's value:\n```JSON\n{\n  \"address\": {\n    \"streetName\": \"string\",\n    \"houseNumber\": \"int\"\n  },\n  \"house\": {\n    \"address\": \"address\"\n  }\n}\n```\nMore about defining state types can be found here.\n### actions:\nActions is how the server and client communicate. Here you can define which client data you want the server to be aware of in order to react with the assigned behaviour. Defining actions is very similar to defining the state except for some additional limitations. Only Go's basic types, and type references in the form of IDs can be used as values.\n```JSON\n{\n  \"buildNewHouse\": {\n    \"owner\": \"personID\",\n    \"streetName\": \"string\",\n    \"houseNumber\": \"int\"\n  }\n}\n```\nThe generator creates all necessary structures for you to simply tell the server how to react to a received action. The `params` struct will contain the defined data. Make use of your personal API to manipulate the `engine`'s state. The engine will keep track of all the changes you have made and tell the server to broadcast the changes to all connected clients automatically. \n```golang\n// ...\nvar actions = state.Actions{\n\tBuildNewHouse: func(params state.BuildNewHouseParams, engine *state.Engine) {\n\t\thouse := engine.CreateHouse()\n\t\taddress := house.Address()\n\t\taddress.SetStreetName(params.StreetName)\n\t\taddress.SetHouseNumber(params.HouseNumber)\n\t},\n}\n// ...\n```\n### responses:\nSometimes you may want to send back data to the client who sent the action. This can be done with responses. Defining responses works exactly the same way as defining actions. Assigning a response to an actions works by giving the response the same name as the action:\n```JSON\n{\n  \"actions\": {\n    \"buildNewHouse\": {\n      \"streetName\": \"string\",\n      \"houseNumber\": \"int\"\n    }\n  }\n}\n{\n  \"responses\": {\n    \"buildNewHouse\": {\n      \"houseID\": \"houseID\",\n    }\n  }\n}\n```\nNow we can tell the server to return data to the client.\n```golang\n// ...\nvar actions = state.Actions{\n\tBuildNewHouse: func(params state.BuildNewHouseParams, engine *state.Engine) state.BuildNewHouseResponse {\n\t\thouse := engine.CreateHouse()\n\t\taddress := house.Address()\n\t\taddress.SetStreetName(params.StreetName)\n\t\taddress.SetHouseNumber(params.HouseNumber)\n\t\t// `ID()` is a getter method to acces the ID of an entity\n\t\treturn state.BuildNewHouseResponse{houseID: house.ID()} // \u003c- return data to client\n\t},\n}\n// ...\n```\n\n## State Structure and Updates:\nUpdates are assembled in a tree-like structure, containing only entities that have updated or who's children have updated. In the action section we have learned how to create a new entity of the `house` type. Creating an entity automatically creates all its children with default values, even if they are not modified. It is just what you'd expect from Go. So the tree update of just the `engine.CreateHouse()` call alone woud look like this:\n```JSON\n{\n    \"house\": {\n        \"1\": {\n            \"address\": {\n                \"id\": 2,\n                \"streetName\": \"\",\n                \"houseNumber\": 0,\n                \"operationKind\": \"UPDATE\"\n            },\n            \"operationKind\": \"UPDATE\"\n        }\n    }\n}\n```\nThis is the data every connected client would receive as result of a `engine.CreateHouse()` call. You can see how each created entity has a `operationKind:\"UPDATE\"` value. This tells the client that this entity is new or has updated since the last received update.\n\nImagine you defined a second action with the name `changeHouseNumber` which behaves like this:\n```golang\nvar actions = state.Actions{\n\t// ...\n\tChangeHouseNumber: func(params state.ChangeHouseNumberParams, engine *state.Engine) {\n\t\thouse := engine.House(params.HouseID)\n\t\thouse.Address().SetHouseNumber(params.NewHouseNumber)\n\t},\n}\n```\nTriggering this action with a message would result in the following tree update:\n```JSON\n{\n    \"house\": {\n        \"1\": {\n            \"address\": {\n                \"id\": 2,\n                \"streetName\": \"\",\n                \"houseNumber\": 1,\n                \"operationKind\": \"UPDATE\"\n            },\n            \"operationKind\": \"UNCHANGED\"\n        }\n    }\n}\n```\nAs the the `house` entity itself has not updated, but only its child `address`, it maintains the `operationKind:\"UNCHANGED\"` value. This way the client can tell that the `house` entity has remained the same since the last update.\n\nWhen an entity is deleted, all its children which are not references will be deleted as well. Eg. delete a house (`engine.DeleteHouse(1)`) in this example:\n```JSON\n{\n  \"address\": {\n    \"streetName\": \"string\",\n    \"houseNumber\": \"int\"\n  },\n  \"house\": {\n    \"address\": \"address\"\n  }\n}\n```\nwould produce the following update:\n```JSON\n{\n    \"house\": {\n        \"1\": {\n            \"address\": {\n                \"id\": 2,\n                \"streetName\": \"\",\n                \"houseNumber\": 0,\n                \"operationKind\": \"DELETE\"\n            },\n            \"operationKind\": \"DELETE\"\n        }\n    }\n}\n```\n\n### How Slices Work:\nSlices behave like you'd expect slices to work. However, to make all entity paths within a tree structure immutable, slices are marshalled as maps. This way we can use the entity's ID instead of its index which could shift during entity modification. In a scenario where your config looks like this:\n```JSON\n{\n  \"address\": {\n    \"streetName\": \"string\",\n    \"houseNumber\": \"int\"\n  },\n  \"house\": {\n    \"address\": \"address\",\n    \"residents\": \"[]person\"\n  },\n  \"person\": {\n    \"name\": \"string\"\n  }\n}\n```\nand you trigger an action which looks like this:\n```golang\n// ...\nvar actions = state.Actions{\n\tAddResidentToHouse: func(params state.AddResidentToHouseParams, engine *state.Engine) {\n\t\thouse := engine.House(params.HouseID)\n\t\thouse.AddResident()\n\t},\n}\n// ...\n```\nthis would be the emitted update:\n```JSON\n{\n    \"house\": {\n        \"1\": {\n            \"residents\": {\n                \"2\": {\n                  \"id\": 2,\n                  \"name\": \"\",\n                  \"operationKind\": \"UPDATE\"\n                }\n            },\n            \"operationKind\": \"UPDATE\"\n        }\n    }\n}\n```\n(note how the `house` has `operationKind:\"UPDATE\"` as its `residents` field got modified)\n\nAs you can see even though `residents` is defined as slice, and a getter call of `house.Residents()` would retrieve a slice of `person`, the field is marshalled as if it was a map. This way this particular `person` will always have the same path within the tree throughout it`s entire lifecycle.\n\nRemoving an entity from a slice does not delete the entity straight away. The entity persists with `operationKind:\"DELETE\"` so the client knows the being deleted. The actual deletion of the entity happens in the next update cycle. So triggering the following action:\n```golang\n// ...\nvar actions = state.Actions{\n\tRemoveResidentFromHouse: func(params state.RemoveResidentFromHouseParams, engine *state.Engine) {\n\t\thouse := engine.House(params.HouseID)\n\t\thouse.RemoveResident(2)\n\t},\n}\n// ...\n```\nwould produce this update:\n```JSON\n{\n    \"house\": {\n        \"1\": {\n            \"residents\": {\n                \"2\": {\n                  \"id\": 2,\n                  \"name\": \"\",\n                  \"operationKind\": \"DELETE\"\n                }\n            },\n            \"operationKind\": \"UPDATE\"\n        }\n    }\n}\n```\n\n# Advanced Types:\n## Type References:\nSometimes you want an entity to have a certain value, but not necessarily own that value, as the value is an entity that exists on itself, and not as a child of another entity. This can be done by using references. An example that would make its usefullness clear would be this one:\n```JSON\n{\n    \"menu\": {\n        \"dishes\": \"[]*dish\",\n        \"glutenFree\": \"[]*dish\",\n        \"vegetarian\": \"[]*dish\",\n        \"todaysDeal\": \"*dish\"\n    },\n    \"dish\": {\n        \"name\": \"string\",\n        \"ingredients\": \"[]string\"\n    }\n}\n```\nRead [here](https://github.com/jobergner/backent-cli#api-reference) on how to use the API to handle references.\n\n## The `anyOf` Type:\nThe `anyOf` type is a Quality of Life feature which lets you define fields to contain more than one type. This brings great flexibility with no additional overhead:\n```JSON\n{\n    \"farm\": {\n        \"owner\": \"string\",\n        \"animals\": \"[]anyOf\u003cchicken,cow,pig\u003e\"\n    },\n    \"chicken\": {\n        \"eggsPerDay\": \"int\"\n    },\n    \"cow\": {\n        \"weight\": \"float64\"\n    },\n    \"pig\": {\n        \"name\": \"string\"\n    }\n}\n```\nRead [here](https://github.com/jobergner/backent-cli#api-reference) on how to use the API to handle `anyOf` types.\n\n# Side Effects:\nThe server `Start` method accepts a `SideEffects` object with the `OnDeploy` and `OnFrameTick` methods.\n```golang\nvar sideEffects = state.SideEffects{\n\tOnDeploy:    func(engine *state.Engine) {},\n\tOnFrameTick: func(engine *state.Engine) {},\n}\n```\n### OnDeploy\nIs called as soon as the server starts. This is a good opportunity to create entities.\n### OnFrameTick\nIs called after all actions for a frame tick a processed.\n\n# API Reference\n## getters\nThe value of every field can be retrieved by calling the name of the field. Given the following config:\n```JSON\n{\n  \"address\": {\n    \"streetName\": \"string\",\n    \"houseNumber\": \"int\"\n  },\n  \"house\": {\n    \"address\": \"address\",\n    \"residents\": \"[]person\"\n  },\n  \"person\": {\n    \"name\": \"string\"\n  }\n}\n```\nthe values can be retrieved like this:\n```golang\nhouse := engine.House(id)                      // house\nalsoHouse, exists := engine.House(id).Exists() // if you can not be sure wether the element exists you can check\nresidents := house.Residents()                 // []person\naddress := house.Address()                     // address\nstreetName := house.Address().StreetName()     // string\nname := house.Residents()[0].Name()            // string\n```\nIn the case of a referenced value:\n```JSON\n{\n    \"menu\": {\n        \"todaysDeal\": \"*dish\"\n    },\n    \"dish\": {\n        \"name\": \"string\",\n        \"ingredients\": \"[]string\"\n    }\n}\n```\nretrieve the values like this:\n```golang\nmenu := engine.Menu(id)                           // menu\ndealRef := menu.TodaysDeal()                      // reference object of dish, bool whether its set\nalsoDealRef, isSet := menu.TodaysDeal().IsSet()   // if you can not be sure wether the reference is set you can check\ndeal := dealRef.Get()                             // dish\n```\n\nIn case of fields with `anyOf` types:\n```JSON\n{\n    \"farm\": {\n        \"owner\": \"string\",\n        \"cutestResident\": \"anyOf\u003cchicken,cow,pig\u003e\"\n    },\n    \"chicken\": {\n        \"eggsPerDay\": \"int\"\n    },\n    \"cow\": {\n        \"weight\": \"float64\"\n    },\n    \"pig\": {\n        \"name\": \"string\"\n    }\n}\n```\nretrieve the values like this:\n```golang\nfarm := engine.Farm(id)                   // farm\ncutestResident := farm.CutestResident()   // chicken|cow|pig\nanimalKind := cutestResident.Kind()       // \"Chicken\"|\"Cow\"|\"Pig\"\ncow := cutestResident.Cow()               // cow\n// if you know of which kind the animal is you can retrieve it directly\ncow := farm.CutestResident().Cow()        // cow\n```\n\nIf you try to retrieve a value where there is none, all manipulations applied to this entity will have no effect.\nThis can happen during the following curcumstances:\n```JSON\n{\n    \"foo\": {\n        \"bam\": \"*bar\",\n        \"bal\": \"anyOf\u003cbar,baz\u003e\"\n    }\n}\n``` \n```golang\nfoo := engine.Foo(123)           // foo with id 123 may not exist\nbam := foo.Bam()                 // bam may not be set\nbalRef := foo.Bal()\nbar := balRef.Bar()              // bal may be of type baz and not bar\n```\n\n## creators\nThe `engine` has creator methods for all entities. They are as straightforward as it gets:\n```JSON\n{\n    \"chicken\": {\n        \"eggsPerDay\": \"int\"\n    },\n    \"cow\": {\n        \"weight\": \"float64\"\n    },\n    \"pig\": {\n        \"name\": \"string\"\n    }\n}\n```\n```golang\nchicken := engine.CreateChicken()\ncow := engine.CreateCow()\npig := engine.CreatePig()\n```\n## deleters\nYou can delete created entitys just as easily as you created them:\n```JSON\n{\n    \"chicken\": {\n        \"eggsPerDay\": \"int\"\n    },\n    \"cow\": {\n        \"weight\": \"float64\"\n    },\n    \"pig\": {\n        \"name\": \"string\"\n    }\n}\n```\n```golang\nengine.DeleteChicken(chickenID)\nengine.DeleteCow(cowID)\nengine.DeletePig(pigID)\n```\n\n## setters\nEvery field with a value of one of Go's basic types has a setter method to set the value. The method will always be called `Set\u003cFieldName\u003e`. The following config:\n```JSON\n{\n    \"chicken\": {\n        \"eggsPerDay\": \"int\"\n    },\n    \"cow\": {\n        \"weight\": \"float64\"\n    },\n    \"pig\": {\n        \"name\": \"string\"\n    }\n}\n```\nwill come with these setters:\n```golang\nengine.Chicken(chickenID).SetEggsPerDay(3)\nengine.Cow(cowID).SetWeight(56.4)\nengine.Pig(pigID).SetName(\"Gunter\")\n```\nReferenced values will come with additional setters when they are not part of a slice:\n```JSON\n{\n    \"chicken\": {\n        \"bestFriend\": \"*chicken\",\n    }\n}\n```\n```golang\nchicken := engine.Chicken(id)\nfriendlyChicken := engine.CreateChicken()\n_, isSet := chicken.BestFriend().IsSet()       // false\n\nchicken.SetBestFriend(friendlyChicken.ID())    // sets the reference\nfriendlyChickenRef, _ := chicken.BestFriend()  // the friendly chicken reference\n_, isSet := friendlyChickenRef.IsSet()         // true\nfriendlyChicken := friendlyChickenRef.Get()    // the friendly chicken\n\nchicken.BestFriend().Unset()                   // unsets friendlyChicken as chicken's best friend\n_, isSet = friendlyChickenRef.IsSet()          // false\n```\nFields with `anyOf` values also have additional setters, when they are not references:\n```JSON\n{\n    \"farm\": {\n        \"owner\": \"string\",\n        \"cutestResident\": \"anyOf\u003cchicken,cow,pig\u003e\"\n    }\n}\n```\n```golang\nfarm := engine.Farm(id)\ncutestResidentKind := farm.CutestResident().Kind()   // default is always the first type of the list (\"Chicken\")\nfarm.CutestResident().SetCow()\ncutestResidentKind = farm.CutestResident().Kind()    // \"Cow\"\n```\n## adders\nAdders are methods to add entitys to fields with slice values. These are the different variants of slices that exist:\n```JSON\n{\n    \"person\": {\n        \"ensurances\": \"[]ensurance\",\n        \"friends\": \"[]*person\",\n        \"nickNames\": \"[]string\"\n    }\n}\n```\nThe adders look like this:\n```golang\nperson := engine.Person(id)\nnewEnsurance := person.AddEnsurance()  // returns the newly created ensurance\n\nnewFriend := engine.CreatePerson()\nperson.AddFriend(newFriend.ID())       // new friend added, no return\n\nperson.AddNickNames(\"peter\", \"pete\")   // since nickNames is a slice of a basic type AddNickNames is a variadic function\n```\n\n## removers\nJust like you can add to fields with slice values, you can also remove entitys:\n```JSON\n{\n    \"person\": {\n        \"ensurances\": \"[]ensurance\",\n        \"friends\": \"[]*person\",\n        \"nickNames\": \"[]string\"\n    }\n}\n```\nRemovers will always return the entity they are called on for convenient method chaining:\n```golang\nperson := engine.Person(id)\n_ = person.RemoveEnsurance(ensuranceID)   // returns person, just like all removers\n\nperson = person.RemoveFriend(personID)\n\nperson.RemoveNickNames(\"peter\", \"pete\")\n```\n\n## meta fields\nevery entity comes with meta fields that you can access freely. Currently the only meta fields are `Path()` and `ID()`:\n```JSON\n{\n  \"address\": {\n    \"streetName\": \"string\",\n    \"houseNumber\": \"int\"\n  },\n  \"house\": {\n    \"address\": \"address\",\n  },\n}\n```\n```golang\naddress := engine.CreateHouse().Address()\n\nhouseID := house.ID()                   // 1\nhousePath := house.Path()               // \"$.house.1\"\n\naddressID := address.ID()               // 2\naddressPath := address.Path()           // \"$.house.1.address\"\n```\n\n## Config Restrictions and their Validation Error Messages\n### structural:\n| Error           | Text                                                             | Meaning                                                         |\n| --------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |\n| ErrIllegalValue | value assigned to key \"{KeyName}\" in \"{ParentObject}\" is invalid | An invalid value was defined (nil, \"\", List, Object in Object). |\n\u003cbr/\u003e \n\n### syntactical:\n| Error                 | Text                                                                         | Meaning                                                                               |\n| --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |\n| ErrIllegalTypeName    | illegal type name \"{KeyName}\" in \"{ParentObject}\"                            | A type was named without adhering to syntax limitations (e.g. \"fo$o\", \"func\", \"\u003c-+\"). |\n| ErrInvalidValueString | value \"{ValueString}\" assigned to \"{KeyName}\" in \"{ParantObject}\" is invalid | An invalid value was assigned to a key                                                |\n\u003cbr/\u003e \n\n### logical:\n| Error                 | Text                                                          | Meaning                                                              |\n| --------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------- |\n| ErrTypeNotFound       | type with name \"{TypeName}\" in \"{ParentObject}\" was not found | A type was referenced as value but not defined anywhere in the data. |\n| ErrRecursiveTypeUsage | illegal recursive type detected for \"{RecurringKeyNames}\"     | A recursive type was defined.                                        |\n| ErrInvalidMapKey      | \"{MapKey}\" in \"{ValueString}\" is not a valid map key          | An uncomparable type was chosen as map key.                          |\n| ErrUnknownMethod      | type \"{TypeName}\" has no method \"{Literal}\".                  | An unknown method was attempted to be referenced.                    |\n\u003cbr/\u003e \n\n### thematical:\nDespite the fact that each of these errors would find a place in one of the above mentioned categories, they are listed separately from them since they are specific to the use case, and not related to the validation of actual go declarations at all.\n| Error                        | Text                                                                                         | Meaning                                                                                                                          |\n| ---------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |\n| ErrIncompatibleValue         | value \"{ValueString}\" assigned to \"{KeyName}\" in \"{ParentObject}\" is incompatible.           | The assigned value can't be used, as only golang's basic types, self defined types, and slices and pointers of them can be used. |\n| ErrNonObjectType             | type \"{TypeName}\" is not an object type.                                                     | The defined type is not an object.                                                                                               |\n| ErrIllegalCapitalization     | {type/field name} \"{literal}\" starts with a capital letter.                                  | A type or field name starts with a capital letter, which is not allowed.                                                         |\n| ErrConflictingSingular       | \"{KeyName1}\" and \"{KeyName2}\" share the same singular form \"{Singular}\".                     | Due to the way state will be used two field names cannot have the same singular form.                                            |\n| ErrUnavailableFieldName      | \"{KeyName}\" not an available name.                                                           | Due to internal usage of this FieldName it is unavailable.                                                                       |\n| ErrDirectTypeUsage           | the type \"{TypeName}\" was used directly in \"{ActionName}\" instead of its ID (\"{TypeName}ID\") | Only IDs of types are available in actions                                                                                       |\n| ErrIllegalPointerParameter   | the parameter \"{FieldName}\" in \"{ActionName}\" contains a pointer value                       | Pointers can not be used as parameter as it would not make any sense                                                             |\n| ErrTypeAndActionWithSameName | type and action \"{Name}\" have the same name                                                  | Types and Actions with the same name would cause conflicts in the generated code                                                 |\n| ErrInvalidAnyOfDefinition    | \"{valueString}\" is not a valid `anyOf` definition                                            | anyOf definitions can not have single or duplicate types and must be in alphabetical order                                       |\n| ErrResponeToUnknownAction    | there is no action defined for response \"{ResponseName}\"                                     | a response can only be defined with the same name as the action it belongs to                                                    |\n\n\n# For Developers\n\n## Getting started:\n```\n# install necessary dependencies\nbash bootstrap.sh;\n\n# generate necessary files (you may ignore output if its not an error)\ngo generate;\n\n# run tests\ngo test ./...\n```\n\n## Project Structure\n| location                                               | description                                                                                                                                                                               |\n| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `/assets`                                              | assets for README                                                                                                                                                                         |\n| `/example.config.json`                                 | used for generating examples in end2end testing and `/integrationtest/state`                                                                                                              |\n| `/examples/application`                                | contains an example of a application                                                                                                                                                      |\n| `/examples/application/server`                         | serves as an example for a server and is a source for copying code into `copied_from_examples.go` during `go generate`                                                                    |\n| `/examples/application/server/gets_generated.go`       | this file contains the only server related declarations that are not copy pasted, but will be generated during runtime based on the config                                                |\n| `/examples/application/server/state.go`                | engine \u0026 API generated with `-engine_only` flag during `go generate`, required for server example to run. Generated code is based on `example.config.json`                                |\n| `/examples/configs`                                    | contains examples for configs, same as `example.config.json`, but in `go`. its what `/examples/engine/` and `/examples/application/server/gets_generated.json` and all tests are based on |\n| `/examples/engine`                                     | serves as an example for an engine \u0026 API. Is also a source for copying code during `go generate` as imports are being used and written into `copied_from_examples.go`                     |\n| `/generate`                                            | script to generate `copied_from_examples.go`                                                                                                                                              |\n| `/inspector`                                           | the inspector application (POC)                                                                                                                                                           |\n| `/inspector/build`                                     | contains the built inspector app. the built always needs to be checked in as it's being hosted when calling the `inspect` command                                                         |\n| `/integrationtest`                                     | starts a server and runs an integration test on `go test .`                                                                                                                               |\n| `/integrationtest/state`                               | server \u0026 engine \u0026 API generated based on `example.config.json` during `go generate` to test                                                                                               |\n| `/pkg/ast`                                             | turns valid config.json into AST                                                                                                                                                          |\n| `/pkg/enginefactory`                                   | writes the engine \u0026 API                                                                                                                                                                   |\n| `/pkg/enginefactory/stringified_state_engine_decls.go` | is generated during `go generate`. contains copy-pasted content of `/examples/engine`. Used to test output of `enginefactory` against                                                     |\n| `/pkg/factoryutils`                                    | some utils for code generation                                                                                                                                                            |\n| `/pkg/getstartedfactory`                               | writes the template for the user to copy-paste which is printed during runtime                                                                                                            |\n| `/pkg/serverfactory`                                   | writes declarations for server (what can be seen in `/examples/application/server/gets_generated.go`)                                                                                     |\n| `/pkg/serverfactory/stringified_server_decls.go`       | is generated during `go generate`. contains copy-pasted content of `/examples/application/server/gets_generated.json`. Used to test output of `serverfactory` against                     |\n| `/pkg/testutils`                                       | utils for testing                                                                                                                                                                         |\n| `/pkg/validator`                                       | validates a user's config                                                                                                                                                                 |\n| `/tmp`                                                 | exists as an out target when running `go run .`                                                                                                                                           |\n| `/copied_from_examples.go`                             | contains code created during `go generate` by `/generate`. content is used during runtime. contains all code that is not written based on a config but can be copy-pasted from examples   |\n| `/docker-compose.yml`                                  | contains instructions to start docker containers for end2end testing                                                                                                                      |\n\n\n## The Idea\nThe project's only purpose is to provide the API to a server which broadcasts state changes to connected clients. It is not supposed to persist data, provide tools to deploy itself or any other utility, as the user is enabled to do all these things themselves by making use of the existing functionality. I want the project to be good at one thing, not mediocre at many.\n\nIntentionally very little of what is going on under the hood is exposed. The user should only have to interact with method names that are self explanatory. The API should be as simple as possible while giving all necessary access to the data. The API should also feel very intuitive.\n\nThe idea is to provide the user with a custom API that is easy to use and prevents them from making mistakes. The user should simply not be able to cause unintended behaviour or panics because they used the API in an incorrect way. This is why the user can eg. `get` an entity which does not even exist and call various methods on it without it affecting the state at all. In these cases the API intercepts by returning an entity with `operationKind:\"DELETE\"`, and by design all methods called on an entity with `operationKind:\"DELETE\"` return without actually doing anything, as there is no point in modifying a deleted entity ever.\n\nKnowledge of Go is required to use the tool, but only superficial knowledge. Go was chosen because it's a language that is easy to learn, but allows for writing fast and maintainable code if you are adept at it (but mainly because I like the language if I'm honest). Go also has a neat way of defining types, which makes creating a `config.json` feel very familiar.\n\n## Tests\nBefore running the tests you need to run `go generate` as it generates the necessary expected outputs for some tests (more on that below). However `go generate` takes a bit of time to run, so feel free to only run the commands that generate the specific files you're working on.\n\n## Testing generated Code:\nI'm not entirely convinced that generating tests alongside generated code is the best way to do things, at least not for me. When I'm testing it is nothing more than an attempt at reducing the likelyhood of my tested code being faulty. I make mistakes while writing code, and I also make mistakes while writing tests. So I can never be really sure if my code does what I think it does, but with each test I can try to get closer to certainty.\n\nGenerating tests does not seem to be the right approach for me, as a lot of logic (and developer time!) is involved. That logic comes with additional possible mistakes, which in turn would again require tests. And at this point I have to ask myself: Am I really approximating s state of less faulty code efficiently?\n\nThis is why I decided to use the following approach:\n1. write an example of what the code I want to generate looks like (eg. `examples/engine`)\n2. test that code with hardly any logic involved in the tests themselves (eg. `examples/engine/state_engine_test.go`)\n3. use [decltostring](https://github.com/jobergner/decltostring) to take a snapshot of every declaration in my example code (eg. `enginefactory/stringified_state_engine_decls.go`)\n4. check if my generated code looks like the declarations in my snapshot (eg. `enginefactory/write_adders_test.go`)\n\nWith this approach I've not only avoided spending time writing generators for tests, I've actually saved time overall. Typing out the example code (eg. `examples/engine`) did not really take very long, it made it very easy to test the code that I want to generate, and it doubles as a base for generating tests for my code generators in a no-effort-manner.\n\nI've thought about whether this approach makes my generated code vulnerable to bugs. After all I'm only testing the example code, which is merely one version of what my generated code can look like. With a different `config.json` the generated code may look completely different. However, this is where this way of doing things really shined in my case. Because my code generator itself serves as a test for my code example.\n\nImagine I want to generate adders for number types, so my code example looks like this:\n```golang\nfunc addInts(a, b int) int {\n\treturn a + b\n}\nfunc addFloats(a, b float64) float64 {\n\treturn a - b\n}\n```\nYou may have noticed that I've already made a mistake in `addFloats`. This happens quite often. The tests of my example code do not cover every single function, as it would simply take too much time, and they actually don't have to! Because when I'm writing my generators, I'm simultaneously writing these tests. It will become more clear when you look at the generator for these functions.\n```golang\nfunc generateAdder(typeName string) {\n\tFunc().Id(\"add\" + Title(typeName) + \"s\").Params(Id(\"a\"), Id(\"b\").Id(typeName)).Id(typeName).Block(\n\t\tReturn(Id(\"a\").Op(\"+\").Id(\"b\")),\n\t)\n}\n```\nSo now when I'm trying to match my code example and my generated code during my tests, I will immediately be notified of the mismatched `-`/`+` in `addFloats`. This is why I consider the generator a test itself. It is an additional layer of logic that assumes a certain output, just like a test, and it actually has cought noumerous errors in my example code during development. For the code generator not to catch that error I would have had to make the same mistake of using a `-` instead of a `+` in all adders, and this kind of mistake would have probably (you never know) been cought by the tests of my code example. The relationship between the example code and the code generator is somewhat like a symbiosis.\n\n\n## State Structure\nEntities are structured in a relational manner. An assembling step exists which organizes the data in it's real tree-like structure. The `Engine` owns 2 state objects. One, the `state`, holds all existing entities, and the other, `the patch` holds all modified entities. The API will never directly modify the `state`, but take a copy of the entity out of the `state`, modify it, and put it into the `patch`. This way we keep track of which entities changed at the end of a cycle. Changed entities are also marked with `operationKind:\"UPDATE\"` or `operationKind:\"DELETE\"`. With each cycle we take all entities of the `patch` and either put them into `state` and set their `operationKind` to `UNCHANGED` when their `operationKind` was `UPDATE`, or we delete the existing representation of the entity in the `state` if the `operationKind` was `DELETE`.\n```golang\nfor _, player := range engine.Patch.Player {\n\tif player.OperationKind == OperationKindDelete {\n\t\tdelete(engine.State.Player, player.ID)\n\t} else {\n\t\tplayer.OperationKind = OperationKindUnchanged\n\t\tengine.State.Player[player.ID] = player\n\t}\n}\n```\n\nEntities are never aware of their parents, as an entity could always be defined to be a child of entity `X` but also of entity `Y`. Introducing logic for an entity to be aware of its parent would have been not worth the effort and the decrease in maintainability of the project.\n\nThere are 2 different ways of assembling the data. The default way is to assemble a tree which only includes updated entities, but also their parent entities until root is reached. The assembling itself starts from the root, traverses every branch of the tree, and returns a flag whether it has encountered an updated entity. This is why the assembling methods always return 3 values.\n```golang\nplayer, include, hasUpdated := engine.assemblePlayer(playerData.ID, nil, config)\n```\n`player` is the assembled entity, `include` is whether the entity should be included in its parent, or remain `nil`, and `hasUpdated` which signifies whether the entity or any of its children contain updated data.\n\nThe reason why `include` and `hasUpdated` exist the second way of assembling the tree is with the `forceInclude` flag enabled in the `assembleConfig`. While this is the case the assembler includes ALL entities in the tree without regarding whether the data has updated or not.\n\n\n## API Characteristics:\n### be up to date and diesregard non-existing/deleted entities:\nWhen handling entities we always make sure we `get` the most recent version of the entity from the `engine`, so the user never accidentally uses stale data. If the entity could not be found the `get`ter will return an empty entity with `OperationKindDelete`, so the rest of the API is aware that this entity can be disregarded, even if the user accidentally calls modifying methods on it.\n```golang\nfunc (_gearScore gearScore) SetScore(newScore int) gearScore {\n\tgearScore := _gearScore.gearScore.engine.GearScore(_gearScore.gearScore.ID) // make sure most recent data is used, returns entity with `OperationKindDelete` if entity could not be found\n\tif gearScore.gearScore.OperationKind == OperationKindDelete {\n\t\treturn gearScore // return if encountering a non-existing entity\n\t}\n    // ...\n}\n```\n### structure creation like go:\nSimilar to go, when creating an entity, all children of this entity are created with default values.\n```golang\nfunc (engine *Engine) createPlayer(p path, extendWithID bool) player {\n\tvar element playerCore\n\telement.engine = engine\n\telement.ID = PlayerID(engine.GenerateID())\n\telementGearScore := engine.createGearScore(p.gearScore(), false)\n\telement.GearScore = elementGearScore.gearScore.ID\n\telementPosition := engine.createPosition(p.position(), false)\n\telement.Position = elementPosition.position.ID\n\treturn player{player: element}\n\t// ...\n}\n```\n### don't allow `nil` where the cannot be void:\nThe API does not allow for deletion of non-root entitys by using `delete`rs, as this would cause the parent of the deleted entity to loose a child, which would be very unintuitive behaviour. Compare it with this pseudo-Go example. This is what the logic would look like if the API allowed for non-root entities to be deleted\n```golang\ntype Foo struct {\n\tbar int\n}\nfunc main() {\n\tx := Foo{1}\n\tdelete(x, \"bar\")\n\tfmt.Println(x.bar) // panics because void\n}\n```\nInstead the API just returns:\n```golang\nfunc (engine *Engine) DeletePlayer(playerID PlayerID) {\n\tplayer := engine.Player(playerID).player\n\tif player.HasParent {\n\t\treturn\n\t}\n\tengine.deletePlayer(playerID)\n}\n```\n### only show the user what's needed:\nThe user will never handle entities directly. They will always be given a wrapper which has access to the required API methods. You can see in the `get`ters that they all wrap the entities before they return it. The reason why wrappers exist for entities is in case I ever decide I want to marshal entities directly before they are assembled in a tree structure (only exported fields can be marshalled). \n```golang\n// playerCore holds the data\ntype playerCore struct {\n\tID            PlayerID                  `json:\"id\"`\n\t// ...\n}\n\n// player wraps playerCore and has access to the data, while providing the methods to the user\ntype player struct{ player playerCore }\n\n// getter wraps playerCore in player before returning\nfunc (engine *Engine) Player(playerID PlayerID) player {\n\tpatchingPlayer, ok := engine.Patch.Player[playerID]\n\tif ok {\n\t\treturn player{player: patchingPlayer}\n\t}\n\t// ...\n}\n\n// AddItem exists on player, not playerCore\nfunc (_player player) AddItem() item {\n\t// ...\n}\n```\n\n### generated entity types:\nSome types are generated alongside the user-defined entities. This happens when a user references a type `*player` or uses an `anyOf` type like `anyOf\u003cEnemy,Player\u003e`. These generated types exist as containers for the actual entities they hold and provide the user with methods to handle them. In case of reference types, a new reference type is generated for each individual field that holds a reference type:\n```JSON\n{\n    \"player\": {\n        \"item\": \"*item\", // creates the playerItemRef\n        \"friend\": \"*friend\" // creates the playerFriendRef\n    }\n}\n```\nThis is required as no entity is ever aware of their parent, but if a different type is created for each reference, the reference is aware of it's parent's type by desing, and only needs to know the ID of the parent to find it in `state`. We need to be aware of the parent in references because when a reference is `Unset` we loop thorugh all references of that type in the state and find the parent to remove the reference from it. If this was not the case we would have to loop thorugh all entities which can hold references of that entity kind and then check if the reference exists on that parent. My fear was that this could make the api slow and could complicate the code generators. Although I have to admit that this could be a case of premature optimization or decision making based on vague guesses.\n\nanyOf types get created for each unique entity pair\n ```JSON\n{\n    \"player\": {\n        \"fooOrBar\": \"anyOf\u003cbar,foo\u003e\", // creates anyOfBar_Foo\n        \"BarOrFoo\": \"anyOf\u003cbar,foo\u003e\", // does not create a new anyOf type as anyOfBar_Foo is already created\n        \"fooOrBaz\": \"anyOf\u003cbaz,foo\u003e\" // creates anyOfBaz_Foo\n    }\n}\n```\nThis way we can provide only the necessary methods for each kind of anyOf type.\n\nAlongside the anyOf types that get created we generate reference containers of these types. any types have setter methods to replace the current contained child entity with a different one. Eg the type `anyOfPlayer_ZoneItem` owns the `.SetPlayer()` and `.SetZoneItem()` methods which both delete the current child, and create a new one. This however causes problems when you are dealing with references of any types, as a setter method would delete the current child.\nbeing able to call the setters on these references makes no sense. This is why any types have ref wrappers, which are created just before `Get()` is called on the reference. These wrappers exclude the\nsetter methods.\n\n\n## Benchmarks\nthe `examples/engine` has benchmark tests with their record and improvements maintained in `benchmark_results.txt`. \n\n### test cases check list\n- create entity -\u003e Create()\n- delete entity -\u003e Delete()\n- manipulate entity -\u003e SetName()\n- add entity -\u003e AddItem()\n- remove entity -\u003e RemoveItem()\n- set reference -\u003e SetBoundTo()\n- unset reference -\u003e BoundTo().Unset()\n- add reference -\u003e AddGuildMember()\n- remove reference -\u003e RemoveGuildMember()S\n- set anyOf reference -\u003e SetTargetPlayer()\n- unset anyOf reference -\u003e Target().Unset()\n- switch anyOf type in slice -\u003e Interactable.SetItem()\n- switch anyOf type -\u003e SetOriginPosition()\n- add anyOf reference -\u003e AddTargetedByPlayer()\n- remove anyOf reference -\u003e RemoveTargetedByPlayer()\n- creation of reference -\u003e a new reference is created\n- deletion of reference -\u003e a reference is deleted\n- replacement of reference -\u003e an existing reference is replaced with a different one\n- includes entity if reference of reference got updated\n- manages cyclical references\n- manages self referencing entitys\n\n### TODO\n\n- in webclient I need to set OperationKindCreate for the entire tree before emitting as otherwise created children won't have correct operationKind\n- need a way to emit root elements with operationKindCreate (maybe in key 0)\n- go generate also runs decltostring\n- cannot properly remove client on OnClientDisconnect\n- room.Deploy can be called multiple times\n\nBIG CHANGE: REMOVE BROADCASTING\na lot of time and energy went into the broadcasting feature, but I still decided to remove it. The amout of complexity it adds is just not worth it and hard to explain, which made code and documentation harder to reason about.\nadditionally the feature was developed with conflict resolution further down the road in mind. However it turns out that resolving confclits would require the server to know which path of conditionals the client took, which the fundamental architecture of the system does not provide.\nthese curcumstances make the only benefit of broadcasting a more responsive experience for the user, but there are better client side options to achieve this ([prediction and interpolation](https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking)).\n\nconsider broadcasting methods on the controller to be optional, as they can easily be cause for mistakes.\n\n- braodcasting would be a lot more useful if it could handle conflicts. conflicts become a problem when an element remains untouched in one scenario, but gets manipulated in another. Simply defining the unchanged state explicitly won't help because the `BroadcastingClientID` prevents the touched element from being recognized by the client. The server would also need to know which route the client took in a conditional to sent a forced update ONLY when there actually was a conflict. solutions:\n    - can't come up with anything, maybe it should just stay that way\n\n\n- basic types are currently treated differntly from all other types; they are sent regardless of wether they have uptadet or not\n    - solution:\n        - make basic types same as other types: own map in state\n        - represent them in tree as *type (eg. *int)\n        - setters fror them should remain the same\n- BROADCASTING (modifications which do not require verification): !!still need to figure out how to conveniently let user define actions with local-first effects\n    - actions should consist of 2 methods: `emit` and `broadcast`. `emit` is run only on the server server, `broadcast` is run on the clinet \u0026 the server\n    - the `emit` method is the one which will return a response\n    - calling the action should return a channel which will receive the response\n    - EXAMPLES:\n        - client side walking\n        - attack events\n        - targeting (wow)\n    - AFFECTED METHODS:\n        - anything involving creation of entities is not viable (which basically ruins the whole concept because of reference/any wrappers)\n            -\u003e it might not because wrappers are not real entities and their IDs are never exposed\n            ... but what if eg. a reference is created client and server side, but then deleted server side. the deletion object with ID x will not remove the\n            local reference of id y\n            SOLUTION: maybe wrappers should not have own IDs but their IDs should be composed of the underlying \"real\" elements\n            TODO: make sure this is REALLY necessary\n            .. non-ref setters of any types do still create elements maybe rename? nonRef=`Be\u003cType\u003e` ref=remains `Set\u003cType\u003e`\n            -\u003e this would make all `setters` broadcastable\n    - somehow there needs to be a way to send an action to the server but not receive the update for that values as it's changed client side\n        - lol has server side walking =\u003e change gets send to server, response is applied locally\n        - diablo 3 has client side walking =\u003e change gets applied locally, then broadcasted\n    - in the client I somehow need to be able to set certain fields to broadcast mode\n    - IDEA:\n        - every element can be signed with a clientID `{broadcastedBy: \u003cclientID\u003e}`\n        - the client application then filters out all elements which are signed with its `clientID`\n        - if multiple clients sign the same element, the signature gets removed (since the element contains data which no client is fully aware of)\n        - signing element can be done by `engine.startBroadcast()` and `engine.endBroadcast()` (running code `local first` and broadcasting is kind of the same idea, how to create clarity?)\n        - this way braodcasting does not happen action-based, but element-based (freedom for user)\n    - PROBLEM:\n        - how does it stay in sync? I can't be sure local state and remote state are the same at time of modification\n            - the idea of broadcasted events is that it does not require verification from the server\n        - what if an event does usually not require verification from server (walking in diablo3) but does in specific instances (a blockade suddenly appears)\n            - the user needs to program solutions for these types of events themselves, eg. when a blockade is being put up and the player has moved there client side\n            already, the player might have to be hard-reset to the next valid position\n- there is currently no way of sending events (eg. sword swing)\n    - need to be able to send events as sending state like `hasSwung` will not suffice (user would have to set it to false manually after each tick)\n    - event may be triggered multiple times\n    - need event to be reset with each update state\n    - maybe an `_event` flag could let engine know that a certain type needs to be cleared with each update state (events would never have OperationKindUnchanged/OperationKindDelete)\n    - validaiton:\n        - events can only be non-pointer slice (any allowed)\n        - events cannot be referenced\n    - events won't have remove method (or maybe they do idk, but removers cureently mess up slice order)\n- currently you can not reference the same element twice in a slice of references due to tree using referenced element ID as map keys \n- what do I want to achieve?\n    - (consistency) no dependency on previously emitted entities, which means:\n        - -\u003e on (created ref/referencing entity update) assemble referenced entity fully\n        - -\u003e paths can only consist of segments which are present in the current update\n        - -\u003e paths cannot be evaluated at time of creation anymore\n        - -\u003e a ton of more complexity\n    - (simplicity) dependency on previously emitted entities (websockets will never receive out-of-order)\n        - -\u003e will always need a client keep to track and update local state -\u003e user will always have acces to full tree at all times\n        - -\u003e can always acces entities, references will never have to include referenced element\n        - -\u003e since I have path to referenced element, can I determine ReferencedDataStatus by checking if it exists in tree?\ndo i still need assembleCache AND forceIncludeAssembleCache\n        - NO YOU CANT BECAUSE AN ELEMENT CAN BE INCLUDED IF ITSELF OR A DESCENDANT REFERENCES AN ELEMENT THAT HAS UPDATED -\u003e need to know \n        ---- maybe elements which have not updated themselves but have a reference of an updating element should not be included in the tree\n            - -\u003e requires map to collect pointers of referenceElements, and map to collect IDs of updating(even descendants) elements\n                -  -\u003e then in a following process manipulate referenceElement.ReferencedDataStatus depending on referenced element in tree\n            - -\u003e would make assembling a lot faster and simpler\n            - -\u003e would reduce data sent\n            - -\u003e would throw out days of work (recursionCheck (cannot be recursive if referenced entities are not assembled), assembleCache (entities will always be assembled only once)) :(\n    \n- reserved action names \"currentState\"  \"update\"\n- error when using example: (because i need to install mods before running (`go mod tidy`))\n```\nstate.go:8:2: no required module provides package github.com/google/uuid; to add it:\n\tgo get github.com/google/uuid\nstate_easyjson.go:8:3: no required module provides package github.com/mailru/easyjson/jlexer; to add it:\n\tgo get github.com/mailru/easyjson/jlexer\nstate_easyjson.go:7:3: no required module provides package github.com/mailru/easyjson/jwriter; to add it:\n\tgo get github.com/mailru/easyjson/jwriter\nstate.go:11:2: no required module provides package nhooyr.io/websocket; to add it:\n\tgo get nhooyr.io/websocket\njonas@jonas-Recoil-II:~/code/backent_e\n```\n- docker for example test\n- documentation\n\n- build fails because of required modules from github. what do? (cant reproduce)\n- find open port for integratpon test\n- the generated code should prefix user defined names (or in some other way alter them to be unique) so they do not conflict with local variables\n- release tree func (release slices, maps, and the pointers themselves)\n- (this only appeared to be an issue because i didnt consider that the Setters create an entirely new Ref with anyContainer as child. So the entityKind is always empty and the delete mthod of the child is therefore never triggered. For more clarity I added a deleteCurrentChild parameter to the function) SetTargetPlayer (a reference field) calls the `setPlayer` method, which removes the child entity. CRITICAL ERROR!!!\n\n\nmore to come\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjobergner%2Fbackent-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjobergner%2Fbackent-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjobergner%2Fbackent-cli/lists"}