Model Runs: To Do and Code Overview - NetLogo/NetLogo GitHub Wiki
This page lists what's left to do on Model Runs and Review Tab, what could possibly maybe be done in the future and part of what has been done in the past. Model Runs users should feel free to modify this page if needed, but we will be progressively transitioning to regular GitHub issues, so if you find a bug, please fill out a ticket.
At the bottom of this page, you can learn a bit about the code behind Model Runs and Review Tab.
- Multiple line notes not displayed properly in indexed notes JTable (probably needs to write a custom cell renderer/editor for that)
- Not sure how to reproduce yet, but after editing a note in the separate window and trying to edit again, we got an index of bounds exception)
- If you switch between two runs from the same model, you keep your position in the run. If it's different models, you don't.
- General notes textbox does not wrap at word boundaries.
- The button for deleting notes (
) should ask for confirmation.
- Multiple notes for the same frame should be allowed
- (it's already possible to do that with
runs:add-annotation
so the interface should be consistent with that)
- (it's already possible to do that with
- Add "free memory" meter.
- I have a question about what the ideal handling of the following scenario is: user records some ticks, then turns off recording for a while, and then turns recording on again. The question i have is, should there be some indication that there is a gap between frames? i have a strong use case for this ('sandbox' models where the user is constructing a virtual experiment, and then running that experiment), but i need to see how users work with it in real practice. (Corey)
- Maybe a visual marker along the scrubber?
- Perspectives in view
- add something like HubNet view overrides so you can e.g. follow a particular turtle when replaying
- Expose full API for building potential extension
- there already exists a crude extension with the
runs:add-annotation
primitive: https://github.com/NetLogo/Runs-Extension - other possible primitives: add general note, add note at specific tick, show review tab, start and stop recording, read annotations
- there already exists a crude extension with the
- world size & patch size (live update on scrub) (Does this make sense, or is a new run started when world is resized?)
- command center (i.e, capture the inputs/outputs from the Command Center; "command center history" tab in review tab; maybe new type of Actions)
- Mirror input box widget
- This one is tricky because the painting code is tangled with user interactions and the fact that there may be different kinds of inputs makes it even harder. Also, it's almost unused in the model library.
- show if recording is on or off from the interface tab, maybe by adding something to the header of the review tab. (Aditi)
- Display of decimals for ticks could be more sophisticated than that, e.g.: show decimals only if this run contains non-integer ticks.
- Add "Hide review tab" option to the Tools menu.
- allow loading the state from the current run into the interface
- (maybe there is a simple way to do this by running something like
export-world
on ourFakeWorld
and thenimport-world
in "real" World.) - Caveat: only visual state is currently mirrored.
- (maybe there is a simple way to do this by running something like
- Suggestion from Firat: add "Record every n ticks" option. Could be useful for huge models that need to run for a long time. Technically non-problematic: that's already what happens if you turn the recording on/off during a single run. Plotting actions would need to accumulate (which they do). Same for drawing layer actions once we have them.
- add a play button that runs the model run like a movie. Scrubbing is critical to have, but it might also be important to be able to watch it as a movie. (Corey)
- optimize size of run file, in-memory and/or on disk
- Serialize plotting actions in memory? (Not sure that is worth it.)
- compress model runs for disk storage and network transmission using http://docs.oracle.com/javase/6/docs/api/java/util/zip/DeflaterOutputStream.html and DeflaterInputStream?
- Massive drawing layer updates are
slownot as fast as they could be. SeeFireworks.nlogo
.- This is most probably because a new image is generated for each frame that has drawing actions. I need to find a way to keep an image that is updated in place, but coordinating this with the ability to scrub back and forth is non-trivial.
- Note: this was made a lot better (as part of this commit) by avoiding some unneeded
BufferedImages <-> Array[Byte]
conversions. The above idea could still improve things, but it is now much less needed than it was.
- One possible way to improve the general feeling of responsiveness: delegate frame reconstruction to a background thread, and only ask for a new frame if the previously requested one has finished constructing. This way:
- even though the interface panel would lag behind, the scrubber cursor wouldn't
- if a user scrubs all the way to end and back, we might avoid constructing some useless frames located after the final position.
- improve performance by making more use of parallel processing? parallel collections, doing stuff on background threads, etc.
- note also that if the main view was rendered using the view diffs, then the engine wouldn't have to stop while rendering took place
- Changing a plot pen's color in the middle of a run doesn't seem to get recorded and/or replayed properly.
- Drawing layer: add ability to serialize an image inside ImportDrawing action. See discussion at: https://github.com/NetLogo/NetLogo/commit/5f71f122ad54bef540fc30b7f084c41287277750
- A whole bitmap is currently passed as a ReadImage action when using
stamp
. See discussion on this commit. - Note from Seth on the plotting rewrite:
- One of the actions is only there to support the
__plot-pen-hide
and__plot-pen-show
primitives which are undocumented and should maybe just be axed. - Nicolas: As it stands, these primitives are used in the Gridlock HubNet activity in the model library. Maybe they are not strictly needed there, but this is something to address before/if we ever ax them.
- One of the actions is only there to support the
- Allow mirroring full state (not just visual)
In order to allow the replay of a whole model run, we needed to find a way of recording it that would allow reconstructing the visible state of the run at any point in time, while keeping memory and speed requirements inside a reasonable range.
Two different approaches were used to achieve that: “diffing” and “event sourcing”.
By “diffing,” we mean the computing of differences from one state to another. This approach is used to record changes in the visible state of turtles, links, patches, observer, world and the monitor widgets. Those changes are modeled as “births,” “deaths,” and “updates.” The state of the simulation is only scanned for differences when display updates are requested. Those differences are then serialized in memory in a compact custom binary format. The main advantage of this method was that it could be implemented with minimal changes to the core NetLogo engine.
Changes to plots and to the drawing layer, on the other hand, are handled by a separate technique known as “event sourcing.” Whenever something affecting a plot or the drawing layers happens in a simulation, an “action” object describing this change is generated. These objects are stored in “action buffers” and grabbed besides the computed differences when a display update is requested.
Event sourcing has one significant advantage over diffing: it does not require scanning the whole state of the model to detect changes. Implementing it, however, required significant modifications to the subsystems involved. Both plotting and drawing are fairly well isolated subsystems and, as such, good test cases for implementing event sourcing. These implementations having been successful, the option of eventually extending the event sourcing model to other things (e.g., command center i/o?) is on the table.
The code is in two new packages:
-
org.nlogo.mirror
(headless) -
org.nlogo.review
(GUI)
The data structures underlying model runs are defined in Mirroring.scala. See especially the case classes at the top of the file. The representation of recordable (“mirrorable”) objects, as defined by the Mirrorable
trait, is simple and uniform. A mirrorable is just a collection of integer-indexed variables holding AnyRef
s (AnyRef
is Scala-ese for java.lang.Object
).
Note that this code makes no reference to NetLogo specifics (turtles, patches, etc) and has no dependencies (besides the Scala standard library).
The generic mirroring code is connected to actual NetLogo agents in Mirrorables.scala. This is where it is specified which agent variables are actually mirrored — currently, only those variables which directly affect the appearance in the agent in the view.
Test code that exercises both Mirroring and Mirrorables is in TestMirroring.scala. Reading these test cases is a good starting point for seeing how the mirroring code is actually used.
Model runs are serialized to bytes both for writing to disk, and for storage in memory, to save RAM. The serialization format is custom. (We considered using a standard format like Protobuf or MsgPack but it seemed like more trouble than it would be worth. Gzipped JSON is also a possibility, but compression is CPU-intensive and small updates might not compress well.)
To replay model runs on the screen for the user, we need to render the constructed agent state using NetLogo's Java2D-based 2D renderer. The render knows how to render instances of api.World
. In ordinary NetLogo it's rendering an actual agent.World
(which implements the api.World
interface), but in model runs we give it a “fake” World that's backed by the data from a model run. See FakeWorld.scala for all the (rather gory) details.
We have leveraged the existing NetLogo regression testing infrastructure to test the model runs code as well. TestMirroringModels.scala runs all of the several hundred models in our regression test suite (the same models used by TestChecksums), records the model runs, renders them using FakeWorld, and then checks that the resulting bitmap is pixel-for-pixel identical with the bitmap rendered from the real World.
The model runs GUI code is in the org.nlogo.review
package; the main file is ReviewTab.scala. ReviewTab
has the GUI layouts; much of the interesting logic is in the ReviewTabState
class which serves as an intermediary between the GUI code and the underlying mirroring code.
There isn't (yet?) any special provision in either API making access especially convenient, but it's certainly possible and actually pretty straightforward to write code that accesses the model run in memory. Look in App.app.tabs
to find an instance of ReviewTab
. From there you can access the ReviewTabState
which contains a ModelRun
. (ask on netlogo-devel if you need further info or assistance)