Development Information
Anyone is welcome to contribute to Surfer. As Surfer is licensed under EUPL 1.2 it is assumed that your contribution will also follow that license.
Once you find something to contribute, either that feature that you are missing or one of the issues, the pattern follows a regular git-like contribution:
- Fork and clone the repository
- Setup pre-commit
- Create a branch (other than
main
) - Edit code
- Commit code with a sensible commit message
- Push branch
- Create a merge request
- Wait for the change to be merged, including fixing suggestions from reviewers
- Enjoy the new feature!
Pre-Commit
Surfer uses the pre-commit framework to do some basic checking when committing new code locally. By using this, the risk of CI errors is reduced.
This is a Python package, so the instructions below assumes that you have a working Python in your console.
- Install pre-commit:
pip install pre-commit
- In the Surfer source-code directory:
pre-commit install
The first time you commit, there will be things installed, so the time taken can be long. However, the next time no installation is required.
Also note that if something fails, like cargo fmt
has to reformat the code, you will
have to commit again as the pre-commit hook only formatted the code, not committed the
formatted code.
Note that the spelling check does not alter the code, but only points out errors. Hence, these must be manually corrected before committing again.
Tests
To run the tests locally, do
cargo test
When possible, it is nice to have a test of the added code.
As Surfer is graphical to a large extent, we primarily rely on image tests
located in libsurfer/src/tests/snapshots.sh
. These tests send a suitable
set of messages and then takes a snapshot of the screen content which is
the ground truth. Easiest way it to copy a suitable test and change the messages.
After running the coverage test in the CI, either a red or a green vertical line will be present in the code view of the merge request to see which code was executed. Ideally, all new code should be tested, but it is currently not realistic to have that as a strict requirement.
Update Image Tests
If you change something that affects rendering or adds new image tests, you will have to update the test images:
-
Run
cargo test
-
Run
./accept_snapshots.bash
-
Add and commit new images
Or if you for whatever reason only want to update some of the images (if you're incrementally fixing things), copy snapshots/<test>.new.png
to snapshots/<test>.png
(and remove snapshots/<test>.diff.png
).
Note that the test images are compressed using oxipng as part of the pre-commit hook.
Using egui Test Framework
When Surfer started with the graphical testing, egui did not have any testing facilities. Now it does, and it would be much beneficial to use egui_kittest. This would allow not just sending messages but to actually click on things etc, which allows both much higher test coverage and, more importantly, certainty that Surfer works as expected.
If you prefer to write the tests using egui_kittest
that is much appreciated and
clearly not a problem. More a step in the right direction.
Long Compilation Times
Compiling Surfer takes a long time which can be annoying during development. To make compilation faster, you can change lto
to false
and opt-level
to a 0
or 1
towards the end of Cargo.toml
in the root directory. This will speed up compilation at the expense of slightly larger and slower binaries (which is probably OK during development anyway).
Adding Configurations
The preferred pattern for a configuration value that can be set both in the
program and in the config file is to add an Option
-value in the UserState
enum and then query the value first there, and, if not set by the user, take
it from the config. This has two benefits:
- If set by the user in the application, it will saved in the state file.
- If not set by the user, any changes in the config will be reflected when loading a state.
To obtain this, a function similar to the following can be added to libsurfer/state_util.rs
#![allow(unused)] fn main() { #[inline] pub fn show_default_timeline(&self) -> bool { self.user .show_default_timeline .unwrap_or_else(|| self.user.config.layout.show_default_timeline()) } }
and then this method is used everywhere to access the value.
This also requires adding a public method in config.rs
, in this case
show_default_timeline()
, that simply returns the corresponding config
value (which should not be public to avoid overwriting etc).
Performance Measurement
If you want to measure performance, surfer has some features to help out.
By running the command show_performance
, it will show a graph with the total
frame time, as well as the time taken to run various parts of the program.
The bulk of rendering is done in signal_canvas::generate_draw_commands
.
The results of generate_draw_commands
are cached by default, and only
recomputed when the viewport changes.
This makes performance measurement harder, but there is a switch to turn off the cache.
You can either click the "Continuous redraw" checkbox in the performance window,
or run show_performance redraw
to turn off the cache.
Command files
If you are debugging performance issues in a specific situation, you can use command files to automate the setup of surfer. For example, if you want to automatically add waves and turn on performance measurements, you can create `performance.sucl and add command prompt arguments that reproduce the issue to it, then run
surfer <wave file> -c performance.sucl
For example, to check performance with many displayed waves, performance.sucl
may look like this:
show_performance redraw
module_add testbench.top.uut
module_add testbench.top.uut
Optimizations
Remember to run in release mode to get accurate performance measurements.
cargo run --bin surfer --release
Flamegraphs
Flamegraphs can be generated using cargo-flamegraph
CARGO_PROFILE_RELEASE_DEBUG=true ca flamegraph -- examples/picorv32.vcd -c performance.sucl