Visualizer

The visualizer is a tool to better understand your workloads.

Setup

You can follow along using the square_eater example by directly going to the visualizer page and clicking on "Example".

For your own workloads, you'll need to create a .json file containing all workloads you want to inspect.
You'll need the serde1 feature on shipyard and serde_json as a dependency.
Then you can run this snippet after the workloads have been added to the World.

#![allow(unused)]
fn main() {
// Add your workloads to the World anywhere before

std::fs::write(
    "drop_me.json",
     serde_json::to_string(&world.workloads_info()).unwrap(),
)
.unwrap();
}

Simply drag and drop the generated .json file to the visualizer page.

Workload selection

Workloads are selected at the top left of the page when you have more than one.

Workload selection

Access Info

This first panel lists all systems and components present in the selected workload.
For the square_eater example this would be this list:

Systems and components list

System

Clicking on a system highlights the components it borrows.
In red the exclusive access, in blue the shared ones.

Components highlights

Component

The same can be done on components to highlight all systems that borrow them.

Systems highlights

Both of these features can be used to identify forced sequential access.
For example AllStorages prevents parallelism, maybe some systems borrowing it could instead borrow individual storages.

Editor

This second panel presents the different batches that compose the workload.
A batch is a list of systems that will attempt to run in parallel when executed.
There is no ordering within a batch.

Implicit ordering

When a system isn't ordered manually, the scheduler will use the source code as reference in addition to the storages borrowed to create an order.
For example, square_eater defines its systems like this:

#![allow(unused)]
fn main() {
(
    counters,
    move_player,
    move_square,
    grow_square,
    spawn,
    collision,
    clean_up,
    check_end_floor.into_workload_try_system().unwrap(),
    render,
)
.into_workload()
}

No manual ordering, so the scheduler will try a top to bottom order.
Sadly, no parallelism can happen in this workload. Each system is assigned a different batch.

You can inspect which type made a system part of a different batch by hovering the red link between systems.
In the case of counters and move_player, we can see that they both borrow Player exclusively.

Implicit conflict

Manual ordering

Using tag, before_all or after_all functions allow us to exit the source code ordering.
For this example, we'll slightly modify the square_eater workload:

#![allow(unused)]
fn main() {
(
    counters,
    move_player,
    move_square,
    grow_square.after_all(move_square).before_all(collision),
    spawn.after_all(move_square).before_all(collision),
    collision,
    clean_up,
    check_end_floor.into_workload_try_system().unwrap(),
    render,
)
.into_workload()
}

This modification doesn't affect the order, we simply force grow_square and spawn to be between move_square and collision.
It may not change the end result, but the constraints between the systems changed.

Manual ordering

If we inspect one of the green link, we find the same information we just added.

Manual constraint

You may also have noticed a red line in the middle.
Using manual ordering doesn't opt out of storage access check (as it could lead to UB).

Manual conflict

Controls

Controls are available at the bottom right of the page.

Controls

The slider is the zoom level, it can also be changed with the mouse wheel (without any modifier key).