feat!: replace dependency graph with reactive invalidation#5292
feat!: replace dependency graph with reactive invalidation#5292
Conversation
5e27ac7 to
5cfd74d
Compare
There was a problem hiding this comment.
Pull request overview
This PR replaces the engine’s coarse dependency-graph recomputation with a reactive invalidation model by introducing observable state primitives in alchemist-api and migrating engine/model code to reschedule based on fine-grained observation.
Changes:
- Introduces observables (including observable collections) and lifecycle-aware observation primitives, and updates core APIs (
Environment,Node,Condition,Actionable) accordingly. - Integrates reactive dependency invalidation into the engine execution path and removes the old dependency-graph approach.
- Migrates multiple incarnations, utilities, UI components, and tests to the new observable APIs (e.g.,
getCurrentPosition,nodeCount.current,getNeighborhood(...).current).
Reviewed changes
Copilot reviewed 172 out of 174 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| gradle/libs.versions.toml | Adds kotlinx immutable collections dependency version. |
| build.gradle.kts | Propagates alchemist.engine system property to tests. |
| alchemist-web-renderer/src/jvmTest/kotlin/it/unibo/alchemist/boundary/server/utility/ToNodeSurrogateTest.kt | Updates to getCurrentPosition API. |
| alchemist-web-renderer/src/jvmMain/kotlin/it/unibo/alchemist/boundary/webui/server/surrogates/utility/ToNodeSurrogate.kt | Uses getCurrentPosition for node surrogate. |
| alchemist-test/src/main/kotlin/it/unibo/alchemist/test/GlobalTestReaction.kt | Implements reactive canExecute observation + lifecycle/dispose. |
| alchemist-test/src/main/kotlin/it/unibo/alchemist/test/Assertions.kt | Uses getCurrentPosition in environment equality assertions. |
| alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/monitor/impl/NodeTracker.kt | Updates displayed position to getCurrentPosition. |
| alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt | Updates position + nodeCount.current usage in rendering. |
| alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/wormhole/impl/MapWormhole.java | Uses getCurrentPosition for zoom fit. |
| alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java | Uses getCurrentPosition and neighborhood .getCurrent(). |
| alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawSmartcam.java | Uses getCurrentPosition for view calculations. |
| alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawPedestrianPath.java | Uses getCurrentPosition for path tracking. |
| alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/AbstractDrawOnce.java | Uses getCurrentPosition for marker visibility. |
| alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/api/Effect.java | Uses getCurrentPosition in default apply method. |
| alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraSee.kt | Uses getCurrentPosition when building visible nodes. |
| alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraInjectVisibleNodeClosestToDistance.kt | Uses getCurrentPosition for heading-based placement. |
| alchemist-physics/src/test/kotlin/it/unibo/alchemist/model/physics/environments/TestEuclideanPhysics2DEnvironment.kt | Updates tests to getCurrentPosition. |
| alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/reactions/PhysicsUpdate.kt | Makes physics update reaction reactive + lifecycle/dispose. |
| alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/ContinuousPhysics2DEnvironment.kt | Uses internal position retrieval consistently. |
| alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/AbstractLimitedContinuous2D.kt | Uses getCurrentPosition for movement constraints. |
| alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/actions/HeadTowardTarget.kt | Uses getCurrentPosition for heading computation. |
| alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/InfluenceSphere2D.kt | Uses getCurrentPosition for influence shape origin. |
| alchemist-physics/src/main/java/it/unibo/alchemist/model/physics/environments/MuseumHall.java | Uses retrievePosition for movement/printing. |
| alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt | Uses nodeCount.current and neighborhood .current. |
| alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/actions/TestTargetMapWalker.kt | Updates position assertions to getCurrentPosition. |
| alchemist-maps/src/main/kotlin/it/unibo/alchemist/model/maps/actions/RandomTargetInPolygonOnMap.kt | Uses getCurrentPosition for collision target updates. |
| alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/movestrategies/speed/StraightLineTraceDependantSpeed.java | Uses getCurrentPosition for distance computation. |
| alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/linkingrules/LinkNodesWithinRoutingRange.java | Adjusts generics for node stream typing. |
| alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/environments/OSMEnvironment.java | Uses retrievePosition when computing routes. |
| alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestSpecificPositions.kt | Uses getCurrentPosition in YAML loading test. |
| alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt | Uses getCurrentPosition and neighborhood .current. |
| alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt | Uses nodeCount.current and neighborhood .current. |
| alchemist-loading/src/test/java/it/unibo/alchemist/model/TestYAMLLoader.java | Uses getCurrentPosition in loader tests. |
| alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java | Uses nodeCount.getCurrent() and getCurrentPosition. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/util/GraphStreamSupport.kt | Uses nodeCount.current when building linking rule. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/deployments/CloseToAlreadyDeployed.kt | Uses getCurrentPosition when extracting sources. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NumberOfNodes.kt | Exports nodeCount.current. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt | Uses nodeCount.current + getCurrentPosition. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodeDegree.kt | Uses neighborhood .current for degree. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt | Uses getCurrentPosition and nodeCount.current. |
| alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt | Uses nodeCount.current + getCurrentPosition. |
| alchemist-loading/build.gradle.kts | Removes Arrow dependency from loading module. |
| alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/conditions/ScafiComputationalRoundComplete.scala | Adds observable dependency to condition. |
| alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/actions/SendScafiMessage.scala | Uses neighborhood .getCurrent. |
| alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/actions/RunScafiProgram.scala | Adds observable computational-cycle completion signal. |
| alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/ScafiIncarnationForAlchemist.scala | Uses getCurrentPosition for layer reads. |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java | Uses getCurrentPosition and neighborhood .getCurrent(). |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java | Adds disposables + switches to reactive dependencies. |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/conditions/LsaStandardCondition.java | Reworks validity/propensity using observables. |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/conditions/LsaNeighborhoodCondition.java | Adds fine-grained neighbor molecule-name observation. |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java | Uses getCurrentPosition and neighborhood .getCurrent(). |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaRandomNeighborAction.java | Uses neighborhood .getCurrent(). |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaCountNeighborsAction.java | Uses neighborhood .getCurrent(). |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaAscendingGradientDist.java | Caches neighborhood via onChange subscription. |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java | Uses getCurrentPosition + neighborhood .getCurrent(). |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java | Uses getCurrentPosition + neighborhood .getCurrent(). |
| alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/ILsaNode.java | Adds observable LSA-space and molecule-name observation APIs. |
| alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/properties/ProtelisDevice.kt | Uses getCurrentPosition for layer fallback reads. |
| alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/actions/RunProtelisProgram.kt | Exposes computational-cycle completion as observable. |
| alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistNetworkManager.kt | Uses neighborhood .current and caches neighbor devices set. |
| alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistExecutionContext.kt | Uses getCurrentPosition in context API. |
| alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/incarnations/ProtelisIncarnation.kt | Updates Node stub to include new observable/lifecycle APIs. |
| alchemist-incarnation-protelis/src/main/java/it/unibo/alchemist/model/protelis/conditions/ComputationalRoundComplete.java | Hooks condition validity into program observable completion. |
| alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/reactions/TestNeighborhoodReactionsPropensities.kt | Migrates junction map access to observable maps. |
| alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/molecules/TestMoleculeSwapWithinNeighborhood.kt | Uses neighborhood .current in assertions. |
| alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/properties/TestDeformableCell.java | Uses getCurrentPosition in tests. |
| alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/nodes/TestEnvironmentNodes.java | Uses getCurrentPosition for environment queries. |
| alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/layers/TestBiomolLayer.java | Uses getCurrentPosition in layer test. |
| alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java | Uses nodeCount.getCurrent() + getCurrentPosition. |
| alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/properties/CircularDeformableCell.kt | Switches junctions storage to observable maps. |
| alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/properties/CircularCell.kt | Switches junctions storage to observable maps. |
| alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/properties/Cell.kt | Switches junctions storage to observable maps. |
| alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt | Reimplements condition using reactive neighbor propensities. |
| alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInNeighbor.kt | Uses neighborhood .current and guards empty neighbor set. |
| alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/CellProperty.kt | Refactors junction operations for observable maps. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironmentNoOverlap.java | Uses retrievePosition in overlap checks and movement. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironment.java | Uses retrieved neighborhood + observable junctions map. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/TensionPresent.java | Makes validity/propensity reactive via nodes-in-range observation. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/NeighborhoodPresent.java | Makes neighbor presence validity reactive. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/JunctionPresentInCell.java | Makes junction presence + propensity reactive. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/GenericMoleculeUnderLevel.java | Derives validity/propensity from observable concentration. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/GenericMoleculePresent.java | Derives validity/propensity from observable concentration. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/EnvPresent.java | Reacts to neighborhood changes for environment-node presence. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.java | Removes legacy non-reactive implementation. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java | Makes total-quantity validity/propensity observable-driven. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/AbstractNeighborCondition.java | Refactors neighbor validity/propensity to reactive model. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java | Caches neighborhood-derived list via onChange; uses getCurrentPosition. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInEnv.java | Uses neighborhood .getCurrent() for env nodes. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/CellTensionPolarization.java | Uses getCurrentPosition for vector computations. |
| alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/AbstractNeighborAction.java | Uses neighborhood .getCurrent() in default execute. |
| alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameter.kt | Removes unused imports; updates copyright year. |
| alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/model/timedistributions/TestSimpleNetworkArrivals.kt | Uses neighborhood .current in mocks. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt | Uses nodeCount.current + neighborhood .current for metrics. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/timedistributions/SimpleNetworkArrivals.kt | Uses neighborhood .current for adjacency. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt | Uses getCurrentPosition + nodeCount.current. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/nodes/GenericNode.kt | Adds observable contents + lifecycle/dispose semantics. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/movestrategies/target/FollowTarget.kt | Uses getCurrentPosition. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/FullyConnected.kt | Uses nodeCount.current. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/ConnectViaAccessPoint.kt | Uses neighborhood .current in link decision. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/NoOtherReactionCanExecute.kt | Makes validity reactive via observeCanExecute(). |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/NeighborHasConcentration.kt | Adds new reactive neighborhood concentration condition. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/ContainsMolecule.kt | Makes validity reactive using observeContains. |
| alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/AbstractNonPropensityContributingCondition.kt | Propensity derived from observeValidity(). |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/reactions/ChemicalReaction.java | Adjusts initialization hook method name/signature. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ObstaclesBreakConnection.java | Uses getCurrentPosition during obstacle checks. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ClosestN.java | Uses nodeCount.getCurrent() and adjusts generics. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/conditions/MoleculeHasConcentration.java | Makes validity/propensity reactive via observable concentration. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/conditions/ConcentrationChanged.java | Makes change-detection reactive and updates propensity. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/conditions/AbstractCondition.java | Introduces reactive validity/propensity + inbound observable deps. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/MoveForwardAndTeleport.java | Uses getCurrentPosition for movement base. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractMoveNode.java | Uses getCurrentPosition for movement computation. |
| alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractConfigurableMoveNode.java | Uses getCurrentPosition for target planning. |
| alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt | Updates to getCurrentPosition and neighborhood .current. |
| alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt | Uses getCurrentPosition and neighborhood .current. |
| alchemist-euclidean-geometry/src/test/java/it/unibo/alchemist/model/obstacles/TestContinuous2DObstacle.java | Uses nodeCount.getCurrent() in assertions. |
| alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/movestrategies/RandomTarget.kt | Uses getCurrentPosition supplier. |
| alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/actions/FollowAtDistance.kt | Uses getCurrentPosition in movement. |
| alchemist-euclidean-geometry/src/main/java/it/unibo/alchemist/model/linkingrules/ConnectionBeam.java | Uses getCurrentPosition for obstacle-aware linking. |
| alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/util/DependencyUtils.kt | Adds test utility for dependency assertions. |
| alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestReactiveDependencies.kt | New test asserting reactive reschedule requests. |
| alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestDependencyGraph.kt | Removes legacy dependency-graph test. |
| alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/EngineTest.kt | Adds engine integration test for reactive neighbor condition. |
| alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/AbstractDependencyTest.kt | Refactors dependency test base for new assertions. |
| alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/batch/BatchManager.kt | Adds batching helper for reschedule requests. |
| alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/BatchEngine.kt | Removes dependency-graph updates; integrates reactive rescheduling path. |
| alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/properties/TestPhysicalPedestrians.kt | Uses getCurrentPosition in behavior assertions. |
| alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/environments/TestEnvironmentWithDynamics.kt | Uses nodeCount.current in checks. |
| alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestSteeringBehaviors.kt | Uses getCurrentPosition for distance checks. |
| alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestOrientingBehavior.kt | Uses getCurrentPosition in assertions and path checks. |
| alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestFeelsTransmission.kt | Uses getCurrentPosition in movement assertions. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/steering/Weighted.kt | Uses getCurrentPosition for closest-target selection. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/PhysicalPedestrian2D.kt | Uses getCurrentPosition for shapes/repulsion. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/Cognitive.kt | Uses getCurrentPosition for layer-based danger. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/ReachKnownDestination.kt | Uses getCurrentPosition for graph routing decisions. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/Explore.kt | Uses getCurrentPosition when sampling node density. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/environments/EnvironmentWithDynamics.kt | Overrides getCurrentPosition and syncs body position. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/CognitiveAgentAvoidLayer.kt | Uses getCurrentPosition in danger detection. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractNavigationAction.kt | Uses getCurrentPosition for navigation state. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractGroupSteeringAction.kt | Uses getCurrentPosition for centroid computation. |
| alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/SteeringActionWithTarget.kt | Uses getCurrentPosition for target distance. |
| alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/ObservableMapTest.kt | Adds tests for observable map semantics. |
| alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/LifecycleTest.kt | Adds tests for lifecycle-bound observation. |
| alchemist-api/src/test/kotlin/it/unibo/alchemist/model/TestTerminationPredicateSerialization.kt | Uses nodeCount.current in predicate serialization test. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/lifecycle/LifecycleState.kt | Adds lifecycle state enum. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/lifecycle/LifecycleRegistry.kt | Adds lifecycle registry implementation. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/lifecycle/LifecycleOwner.kt | Adds lifecycle owner + bindTo helper. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/lifecycle/Lifecycle.kt | Adds lifecycle interface. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt | Adds non-idempotent event observable for signals. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Disposable.kt | Adds common disposable abstraction. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt | Adds base class for derived observables. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Node.kt | Adds observable contents + lifecycle/dispose to nodes. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/EuclideanEnvironment.kt | Updates default move to getCurrentPosition. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt | Makes neighborhood/position/node count reactive. |
| alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Actionable.kt | Adds observeCanExecute, lifecycle/dispose, reschedule observable. |
| alchemist-api/src/main/java/it/unibo/alchemist/model/Condition.java | Adds reactive validity/propensity + observable deps + dispose. |
| alchemist-api/src/main/java/it/unibo/alchemist/core/DependencyGraph.java | Removes old dependency-graph API. |
| alchemist-api/build.gradle.kts | Promotes Arrow + kotlinx immutable collections to API deps. |
| .idea/inspectionProfiles/Project_Default.xml | Disables IntelliJ DataFlowIssue inspection. |
Files not reviewed (1)
- .idea/inspectionProfiles/Project_Default.xml: Language not supported
| set(value) { | ||
| field = value | ||
| field.forEach(Disposable::dispose) | ||
|
|
There was a problem hiding this comment.
In the conditions setter you assign field = value and then immediately dispose field, which disposes the new conditions instead of the previously installed ones. This likely breaks subsequent subscriptions (observeInboundDependencies) and makes conditions unusable. Dispose the old field before overwriting it, then wire observers on the new list.
| if (nextEvent.canExecute()) { | ||
| safeExecuteEvent(nextEvent) | ||
| safeUpdateEvent(nextEvent) | ||
| } | ||
|
|
||
| nextEvent.update(currentLocalTime, true, environment) | ||
| scheduler.updateReaction(nextEvent) | ||
| synchronized(scheduler) { | ||
| scheduler.updateReaction(nextEvent) | ||
| } |
There was a problem hiding this comment.
nextEvent.update(currentLocalTime, true, environment) is invoked unconditionally with hasBeenExecuted = true, even when nextEvent.canExecute() is false and the reaction was not executed. This violates the Actionable.update contract and can cause incorrect rescheduling/time-distribution updates. Pass hasBeenExecuted based on whether the reaction actually executed.
| val inner = junctions[junction].currentOrNull() | ||
| inner?.let { | ||
| when (it[neighbor].current.getOrElse { 0 }) { | ||
| 1 -> it.remove(neighbor) | ||
| else -> it[neighbor].currentOrNull()?.minus(1) | ||
| } | ||
| if (it.isEmpty()) { | ||
| junctions.remove(junction) | ||
| } else { | ||
| junctions[junction] = inner | ||
| } |
There was a problem hiding this comment.
In removeJunction, the else -> it[neighbor].currentOrNull()?.minus(1) branch computes a decremented value but never writes it back to the inner map, so counts > 1 will never decrease. Update the map entry (e.g., set the new value) when decrementing, and ensure the updated value is stored in the observable map.
| override fun cloneCondition( | ||
| newNode: Node<Double>, | ||
| newReaction: Reaction<Double>, | ||
| ): AbstractNeighborCondition<Double> = BiomolPresentInNeighbor(environment, node, molecule, concentration) | ||
|
|
There was a problem hiding this comment.
cloneCondition ignores the newNode parameter and rebuilds the condition using the original node, which will attach the cloned condition to the wrong node (and potentially wrong environment context). Recreate the condition with newNode (and the appropriate environment reference) instead.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e27ac7221
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| return Disposable { | ||
| this@bindTo.stopWatching(dataListener) |
There was a problem hiding this comment.
Unregister lifecycle bindings with the correct registrant
The disposable returned by bindTo unregisters with stopWatching(dataListener), but the subscription was created with onChange(lifecycleOwner, ...). Because the registrant keys do not match, dispose() does not actually detach the observer, so repeated rebindings (for example when reactions are reinitialized) accumulate stale callbacks and can trigger duplicate updates/reschedule requests.
Useful? React with 👍 / 👎.
| override fun stopWatching(registrant: Any) { | ||
| observers -= registrant | ||
| observingCallbacks.remove(registrant) | ||
| backing.stopWatching(registrant) |
There was a problem hiding this comment.
Stop union observers using the same registration key
union.onChange registers callbacks in backing with the composite key this to registrant, but union.stopWatching removes only registrant. This mismatch leaves observers subscribed in backing after callers unsubscribe, causing leaked subscriptions and continued callback delivery to supposedly removed watchers.
Useful? React with 👍 / 👎.
| override fun toList(): List<T> = backing | ||
|
|
||
| override fun copy(): ObservableMutableList<T> = ObservableMutableList<T>().apply { | ||
| backing = this@ObservableMutableList.backing |
There was a problem hiding this comment.
Preserve observable size when copying observable lists
copy() duplicates only backing, so the new list’s internal sizeObservable remains at its default 0 even when the copied list is non-empty. Any code that reads copy().observableSize.current right after cloning gets an incorrect value until a later mutation happens to resync it.
Useful? React with 👍 / 👎.
| newPosition == null -> { // removal | ||
| if (node in region.visibleNodes) region.visibleNodes.remove(node) | ||
| regionNodeCenteredIndex.remove(node.id)?.forEachValue { | ||
| regionObservers.remove(it) |
There was a problem hiding this comment.
Avoid removing region observers during list iteration
This method iterates regionObservers and, in the node-removal path, also removes elements from the same list. When node-centered range observers exist, removing a node can trigger ConcurrentModificationException and break the environment update flow. Defer removals or use an iterator-based removal strategy.
Useful? React with 👍 / 👎.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #5292 +/- ##
=========================================
Coverage 61.53% 61.53%
Complexity 14 14
=========================================
Files 2 2
Lines 78 78
Branches 4 4
=========================================
Hits 48 48
Misses 24 24
Partials 6 6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
1da8723 to
c36bbea
Compare
|
Hi @DanySK! 👋 |
134af6e to
efa9acf
Compare
d1caefc to
cd7d649
Compare
|
Hi @DanySK! 👋 |
ea9856f to
2a7f507
Compare
# Conflicts: # .github/workflows/build-and-deploy.yml # .github/workflows/update-ancillary-files.yml # CHANGELOG.md # dokka-cache/ch.qos.logback/logback-classic/1.5.21.list # dokka-cache/ch.qos.logback/logback-classic/1.5.22.list # dokka-cache/ch.qos.logback/logback-classic/1.5.26.list # dokka-cache/de.flapdoodle.embed/de.flapdoodle.embed.mongo/4.21.0.list # dokka-cache/de.flapdoodle.embed/de.flapdoodle.embed.mongo/4.22.0.list # dokka-cache/de.flapdoodle.embed/de.flapdoodle.embed.mongo/4.24.0.list # dokka-cache/it.unibo.alchemist/alchemist-api/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-api/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-api/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-cognitive-agents/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-cognitive-agents/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-cognitive-agents/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-composeui/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-composeui/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-composeui/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-engine/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-engine/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-engine/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-euclidean-geometry/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-euclidean-geometry/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-euclidean-geometry/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-full/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-full/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-full/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-graphql-surrogates/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-graphql-surrogates/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-graphql-surrogates/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-graphql/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-graphql/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-graphql/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-implementationbase/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-implementationbase/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-implementationbase/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-biochemistry/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-biochemistry/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-biochemistry/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-protelis/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-protelis/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-protelis/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-sapere/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-sapere/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-incarnation-sapere/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-loading/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-loading/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-loading/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-maintenance-tooling/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-maintenance-tooling/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-maintenance-tooling/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-maps/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-maps/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-maps/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-physics/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-physics/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-physics/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-sapere-mathexp/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-sapere-mathexp/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-sapere-mathexp/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-smartcam/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-smartcam/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-smartcam/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-swingui/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-swingui/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-swingui/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-test/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-test/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-test/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-ui-tooling/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-ui-tooling/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-ui-tooling/42.3.32.list # dokka-cache/it.unibo.alchemist/alchemist-web-renderer/42.3.18.list # dokka-cache/it.unibo.alchemist/alchemist-web-renderer/42.3.22.list # dokka-cache/it.unibo.alchemist/alchemist-web-renderer/42.3.32.list # gradle/libs.versions.toml # package-lock.json # package.json # settings.gradle.kts # Conflicts: # alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt # Conflicts: # alchemist-api/build.gradle.kts # alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt # alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/loader/LoadingSystem.kt
…ring callback invocation
…l callback Now the "laziness" concept is applied to dervied observables too.
For actions in general, it is better to use an imperative approach and retrieve the current neighborhood instead of having an up-to-date neighborhood version stored locally.
…ency graph fix: remove obsolete reactive engine loading chore(engine): use explicit synchronisation instead of thread contexts
This idea is borrowed by Androidx, where by means of Lifecycle state machines we are able to bound the lifecycle of the dependencies to the lifecycle of the owner (i.e. the registrant), properly disposing and releasing observers references withtout using weak references, hence not impacting too much negatively performance.
…es instead of entire LSASpace
commit cbc430612a9a9063264da4f229fec1f7dc270ecd
Author: S-furi <stefano.furi7@gmail.com>
Date: Fri Feb 20 14:02:00 2026 +0100
perf(api): make use of persistent collections in observable collections
this led to a great improvement in memory usage especially for neighbor-related streams where set of nodes where occupying large chunks of memory.
This usage of persistent structures improved equality checks speed with an overall time reduction of approx a 10% wrt standard kotlin collections usage (from 22s independent test (SAPERE) to 20s)
commit ef9301aed3555d7d97c262a755085ace39bc9c24
Author: S-furi <stefano.furi7@gmail.com>
Date: Fri Feb 20 11:53:17 2026 +0100
perf(engine): introduce `BatchManager` to avoid redundant reschedule requests
commit 6c04f40fcea4ce9f644cf2b9e162b999199d524e
Author: S-furi <stefano.furi7@gmail.com>
Date: Fri Feb 20 11:18:14 2026 +0100
perf(sapere): introduce finer-grained observables for lsa spaces
2a7f507 to
79c0f7e
Compare
|



Summary
This PR supersedes #5131.
It rebases the reactive-engine work on top of the current
masterand preserves the same overall direction: move the engine and the observable state model away from coarse-grained dependency recomputation and toward reactive, fine-grained invalidation.At a high level, this branch:
alchemist-apiDependencyGraph/JGraphTDependencyGraphpathEngineWhy this exists
The original goal from #5131 remains the same:
Main changes
1. Observable model infrastructure
This PR adds a substantial observation layer in
alchemist-api, including:ObservableObservableListObservableMapObservableSetLifecycle,LifecycleOwner,LifecycleRegistry,LifecycleState)These components are then used to expose mutable simulation state in a way that can notify dependent logic incrementally instead of forcing coarse global refreshes.
2. Engine architecture
The branch first introduces a separate reactive engine during development, but the final rebased result does not keep a standalone
ReactiveEngineclass.Instead, the final state:
DependencyGraphJGraphTDependencyGraphEngineAbstractEngineand related support codeThis is important because the PR is no longer “add a second engine implementation”; it is effectively “replace the old engine dependency-management model with a reactive one.”
3. Fine-grained invalidation in core model code
A large portion of the diff updates:
to subscribe to narrower observable sources and react only to relevant changes.
This includes changes in
alchemist-implementationbase, where several conditions and reaction-related classes are updated to consume the new observable semantics.4. SAPERE-focused performance work
The most performance-oriented part of the PR is the SAPERE work:
LsaNodeLsaNeighborhoodConditionLsaStandardConditionSAPEREGradientSAPEREReactionThese changes aim to stop reacting to whole-LSA-space churn when only a targeted molecule or neighborhood-relevant subset changed.
This is likely the most immediately measurable performance improvement in the branch, because SAPERE workloads often stress dependency propagation and neighborhood-sensitive invalidation patterns.
5. Incarnation and integration updates
To keep the engine/model shift coherent, the branch also updates:
This is not incidental churn; most of these changes are required to make the reactive model consistent across the simulation stack.
What changed compared to #5131
This PR should be treated as the replacement for #5131, not as an unrelated follow-up.
Compared to #5131:
mastermasterdependency versions where appropriateEngineValidation status
This branch was rebased with repository hooks enabled.
During the rebase, the only manual conflict resolutions required were:
alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/loader/LoadingSystem.ktgradle/libs.versions.tomlThe resolutions were mechanical and aligned with the final intent of the rebased commits:
Engine(environment)path after the dedicatedReactiveEngineremovalmaster's newer shared dependency versions while preserving the branch’s addedkotlinx-collectionscatalog entriesHow to assess the performance change
There does not appear to be a dedicated microbenchmark suite in the repository, so the best assessment is comparative measurement on representative workloads.
Best candidates to measure
The most relevant areas are:
Practical ways to evaluate
Compare
mastervs this branch using repeated runs of the same workloads.Focus first on SAPERE-heavy tests and simulations, because the final commits specifically optimize fine-grained SAPERE invalidation.
Use wall-clock time, CPU time, allocation rate, and GC pressure as the primary metrics.
Run on the same JVM and machine, ideally multiple times, ignoring the first run if dependency/classloading noise matters.
Concrete approaches
Run targeted Gradle test tasks repeatedly on
masterand this branch:./gradlew --parallel :alchemist-incarnation-sapere:test./gradlew --parallel :alchemist-engine:test./gradlew --parallel :alchemist-cognitive-agents:testFor scenario-level comparisons, build the simulator and execute the same representative simulations on both revisions, then compare:
If deeper profiling is needed, use JFR or an external profiler on identical simulations to confirm that:
What I would expect to improve
If the branch behaves as intended, the most likely gains are:
I would not assume uniform speedups everywhere. Some workloads may be neutral, and broad integration changes like this deserve empirical confirmation rather than a blanket performance claim.
Reference