diff --git a/app/thirdparty/buildoz b/app/thirdparty/buildoz index 2d3a463..7f4e13c 160000 --- a/app/thirdparty/buildoz +++ b/app/thirdparty/buildoz @@ -1 +1 @@ -Subproject commit 2d3a4631c8e1279d5a0eec8dc0d0870f65f7c8db +Subproject commit 7f4e13c5e0f65f729fc2a306054e7d3b07f08328 diff --git a/app/views/editors/KeyframeView.js b/app/views/editors/KeyframeView.js index ac51852..cc9fc9d 100644 --- a/app/views/editors/KeyframeView.js +++ b/app/views/editors/KeyframeView.js @@ -71,11 +71,20 @@ class KeyframeView extends WindozDomContent { this.outputs.btnAddAgent.disabled = true this.outputs.btnRemoveAgent.disabled = true - this.outputs.btnSaveKF.disabled = true + this.outputs.btnSaveKF.disabled = true + this.outputs.btnResetKF.disabled = true this.outputs.kfName.addEventListener('keyup', this.updateKfButtons.bind(this)) this.currentlySelectedAid = null } + deselectSceneAgent(){ + if(!this.currentlySelectedAid) return + const obj3D = this.kfArena.scene.getObjectByName(this.currentlySelectedAid) + if(obj3D) this.kfArena.clearHighlight3DObj(obj3D, this.kfArena.scene) + this.currentlySelectedAid = null + this.outputs.btnRemoveAgent.disabled = true + } + async onChangeAgent(event){ if(this.outputs.agentsSelector.value) this.agentPreview.setAgent(this.outputs.agentsSelector.value) if(!this.outputs.agentsSelector.value) return @@ -85,36 +94,41 @@ class KeyframeView extends WindozDomContent { } else { this.currentAgentType = await this.models.agents.getProperties(this.outputs.agentsSelector.value) this.fillAgentProperties('', this.currentAgentType) - // Deselect any on-scene selection - if(this.currentlySelectedAid){ - this.kfArena.clearHighlight3DObj(this.kfArena.scene.getObjectByName(this.currentlySelectedAid), this.kfArena.scene) - } - this.currentlySelectedAid = null + this.deselectSceneAgent() } } async onChangeKeyframe(event){ if(!this.outputs.keyframesSelector.value) return - let kfData = await this.models.keyframes.getKeyframe(this.outputs.keyframesSelector.value).then(data => data.payload) + await this.loadKeyframe(this.outputs.keyframesSelector.value) + } + + async onResetKF(evt){ + if(!this.currentKeyframe.kfId) return + await this.loadKeyframe(this.currentKeyframe.kfId) + } + + async loadKeyframe(kfId){ + let kfData = await this.models.keyframes.getKeyframe(kfId).then(data => data.payload) this.currentKeyframe = { kfId: kfData.info.ekf_uuid, kfName: kfData.info.ekf_name, prevKfId: kfData.info.ekf_prev_uuid, } this.outputs.kfName.value = kfData.info.ekf_name + this.deselectSceneAgent() this.kfArena.reloadAgents(kfData.agents) + this.outputs.agentProperties.innerHTML = '' + this.outputs.btnAddAgent.disabled = true + this.updateKfButtons() } onclickAgent(obj3D){ const aid = obj3D.name if(this.currentlySelectedAid == aid){ // Deselect - this.kfArena.clearHighlight3DObj(obj3D, this.kfArena.scene) - this.currentlySelectedAid = null - this.outputs.btnRemoveAgent.disabled = true + this.deselectSceneAgent() } else { // Select - if(this.currentlySelectedAid){ - this.kfArena.clearHighlight3DObj(this.kfArena.scene.getObjectByName(this.currentlySelectedAid), this.kfArena.scene) - } + this.deselectSceneAgent() this.currentlySelectedAid = aid this.outputs.btnRemoveAgent.disabled = false if(this.kfArena.agents[aid]) { @@ -139,6 +153,10 @@ class KeyframeView extends WindozDomContent { onRemoveAgent(event){ if(!this.currentlySelectedAid) return this.kfArena.removeAgent(this.currentlySelectedAid) + this.currentlySelectedAid = null + this.outputs.btnRemoveAgent.disabled = true + if(this.currentAgentType) this.fillAgentProperties('', this.currentAgentType) + this.updateKfButtons() } async newAgent(aType, AgentValues){ @@ -151,6 +169,7 @@ class KeyframeView extends WindozDomContent { updateKfButtons(){ if((Object.keys(this.kfArena.agents).length > 0) && (this.outputs.kfName.value.length > 5)) { this.outputs.btnSaveKF.disabled = false } else { this.outputs.btnSaveKF.disabled = true } + this.outputs.btnResetKF.disabled = !this.currentKeyframe.kfId } onPropsChanged(evt, comp){ @@ -230,6 +249,7 @@ class KeyframeView extends WindozDomContent { await this.models.keyframes.save(this.currentKeyframe.kfId, this.kfArena.agents) this.outputs.btnSaveKF.disabled = true + this.updateKfButtons() ui.growl.append('Keyframe saved!','success',3000) setTimeout(() => { this.outputs.btnSaveKF.disabled = false}, 3000) } diff --git a/doc/doc1.json b/doc/doc1.json new file mode 100644 index 0000000..09a54fd --- /dev/null +++ b/doc/doc1.json @@ -0,0 +1,185 @@ +{ + "nodes": [ + { + "id": "concept.p42", + "name": "P42", + "type": "concept", + "scope": "project", + "userDoc": { + "statements": [ + "Project 42 (or P42 for short) is a general purpose simulation engine", + "Its goal is to provide a flexible and powerful tool for simulating and analyzing the widest possible range of complex systems", + "It is a platform for building and running simulations, for analyzing the results of those simulations", + "It aims at allowing users to easily create and spot emerging phenomenons in complex systems" + ], + "humor": [ + "Why 42 ? Well, the author hopes this project will help answering non-obvious questions, and you should know 42 is the answer to THE BIGGEST question." + ] + }, + "developperDoc": { + } + }, + { + "id": "concept.godProcess", + "name": "God process", + "type": "concept", + "scope": "platform", + "userDoc": { + "statements": [ + "A god-process is a process that is responsible for a specific part of the overall management of the simulation", + "Contrary to agents, it is not part of the simulation per-se, as it remains neutral to what happens in the arena of the simulation", + "Contrary to agents, it has access to everything that happens in the arena of the simulation in order to perform its tasks, and is therefore in a \"god-like\" position." + ], + "humor": [ + "Ironically, the author of P42 is a firm non-believer", + "In the unix world, a background process is called a \"daemon\", and therefore, you can call a god-process a \"god-daemon\"" + ] + }, + "developperDoc": { + "constraints": [ + "All god-daemons are kept in separated folders, in /op/p42GodDaemons", + "All god-daemons should depend on the same Redis P42 library that standardizes accesses to Redis Pub/Sub and Redis database", + "God-daemons can have access to the arena bus, the management bus or (generally) both.", + "God-daemons can fire events on the arena bus that will be received by agents", + "God-daemons can fire events on the management bus that will be received by the interface", + "God-daemons can fire events on the management bus that will be received by other god-daemons", + "God-daemons can fire events on the arena bus that will be received by other god-daemons", + "God-daemons must never update agents data directly (that's the agent's job)", + "Agents can make requests to God-daemons via the arena bus, and god-daemon should always reply with a valid response (even if empty or error)" + ] + }, + "relations": [ + { "type": "uses", "target": "concept.arenaBus" }, + { "type": "uses", "target": "concept.managementBus" } + ] + }, + { + "id": "concept.arenaBus", + "name": "Arena Bus", + "type": "concept", + "scope": "platform", + "userDoc": { + "statements": [ + "The Arena Bus is a message bus that is used to communicate between agents and god-processes", + "It is a Redis backed PUB/SUB message bus, and is used to provide communications between agents, between agents and god-processes, and between god-processes", + "For agents, this bus represents their sole window to the simulation's world (arena)", + "For God-daemons, the bus allows to receive agent events (monitoring), send world-state change events to interested agents, and to provide agents with request-reply services" + ] + }, + "developperDoc": { + "constraints": [ + "For scalability, the arena bus can use several different Redis daemons, each one representing a shard of the Arena (bus and agents storages)", + "To respect arena isolation, events on the arena bus shall not cary any information which is not directly related to the arena (agents, god-processes, etc.)" + ] + } + }, + { + "id": "concept.managementBus", + "name": "Management Bus", + "type": "concept", + "scope": "platform", + "userDoc": { + "statements": [ + "The Management Bus is a message bus that is used to communicate between god-processes, and between god-processes and the interface", + "It is a Redis backed PUB/SUB message bus" + ] + }, + "developperDoc": { + "constraints": [ + "For simplicity, the management bus only uses a single Redis daemon", + "In order to keep the arena bus as little loaded as possible, inter-god-daemons events shall use the management bus instead of the arena bus" + ] + } + }, + { + "id": "concept.PRNGs", + "name": "PRNGs", + "type": "concept", + "scope": "agent api", + "userDoc": { + "statements": [ + "PRNGs (Pseudo-Random Number Generator) are provided to agents needing random numbers", + "Agents cannot use any other random number generator than the PRNGs provided to them by the PRGN God-Daemon", + "This ensures reproducibility of the simulation, and prevents agents from using non-reproductible randomness" + ] + }, + "developperDoc": { + "constraints": [ + "Random numbers are provided to agents upon request to the God-Daemon PRNG service", + "Seed based PRNGs (Pseudo-Random Number Generator) are used to generate random numbers for agents", + "PRNGs must ensure a high enough entropy for each agent", + "PRNGs must ensure reproducibility from one run to the next, by storing all seeds with the arena state", + "Agents shall never have access to seeds, nor to any other information that could be used to reproduce the same random numbers", + "PRNGs must be thread-safe, and must be able to handle concurrent requests from multiple agents" + ] + } + }, + { + "id": "feature.horizontal-scaling", + "name": "horizontal scaling", + "type": "feature", + "scope": "performance", + "userDoc": { + "statements": [ + "Horizontally scalable by design, allowing users to easily scale their simulations to handle large amounts of data and agents", + "The limit is only the computing power users are ready top pay for" + ] + }, + "developperDoc": { + "constraints": [ + "All runners are containerized, and are executed by a dedicated container orchestrator, which uses a dedicated Redis that represents a shard of the Arena (bus and agents storages)" + ], + "statements": [ + "Uses a distributed architecture that uses any number of containerized runners that execute agents code, as well as the code of god-processes", + "Communication between agents, god-processes and interface is handled by two REDIS backed PUB/SUB message buses" + ] + } + }, + { + "id": "feature.strongArenaIsolation", + "name": "Strong arena isolation", + "type": "feature", + "scope": "simulation", + "userDoc": { + "statements": [ + "Designed to maintain strong isolation between the arena of the simulation and the rest of the system", + "Agents are isolated from each other, and from the rest of the system to ensure no unwanted data contamination could tamper the agent's behavior" + ] + }, + "developperDoc": { + "constraints": [ + "No event shall be ported bewteen arena and management busses", + "No reply to an agent request and no payload of an arena event should leak any \"overview\" of the simulation state to the agent, neither any management data of the system" + ], + "statements": [ + "Uses two different busses: one for letting agents communicate with each other, or with the god-processes, and a second isolated one for managing the simulation state", + "Likewise, agents internal states and properties belong to the arena, and are stored in a dedicated REDIS database, isolated from the general system databases" + ] + }, + "relations": [ + { "type": "uses", "target": "concept.arenaBus" }, + { "type": "uses", "target": "concept.managementBus" } + ] + }, + { + "id": "feature.reproductibility", + "name": "Reproductibility", + "type": "feature", + "scope": "simulation", + "userDoc": { + "statements": [ + "Designed to ensure that the simulation can be reproduced exactly the same way, from one run to the next", + "Same initial conditions should lead to the same final state, unless the simulation is voluntarily stochastic (agents use non-reproductible randomness)" + ] + }, + "developperDoc": { + "constraints": [ ], + "statements": [ ] + }, + "relations": [ + { "type": "uses", "target": "concept.PRNGs" }, + { "type": "uses", "target": "concept.managementBus" } + ] + } + ] +} \ No newline at end of file