├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ ├── before_deploy.sh │ ├── build.yml │ ├── build_shared.sh │ ├── build_static.sh │ └── test.sh ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── Cross.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── balanced_pb.rs ├── layout_state.rs ├── parsing.rs ├── scene_management.rs ├── software_rendering.rs └── svg_rendering.rs ├── capi ├── Cargo.toml ├── bind_gen │ ├── Cargo.toml │ └── src │ │ ├── c.rs │ │ ├── csharp.rs │ │ ├── java │ │ ├── jna.rs │ │ ├── jni.rs │ │ └── mod.rs │ │ ├── jni_cpp.rs │ │ ├── kotlin │ │ ├── jni.rs │ │ └── mod.rs │ │ ├── main.rs │ │ ├── node.rs │ │ ├── python.rs │ │ ├── ruby.rs │ │ ├── swift │ │ ├── code.rs │ │ ├── header.rs │ │ ├── mod.rs │ │ └── module.modulemap │ │ ├── typescript.rs │ │ ├── typescript.ts │ │ └── wasm_bindgen.rs ├── js │ ├── .gitignore │ ├── Makefile │ ├── node │ │ └── package.json │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json └── src │ ├── analysis.rs │ ├── atomic_date_time.rs │ ├── attempt.rs │ ├── auto_splitting_runtime.rs │ ├── blank_space_component.rs │ ├── blank_space_component_state.rs │ ├── command_sink.rs │ ├── component.rs │ ├── current_comparison_component.rs │ ├── current_pace_component.rs │ ├── delta_component.rs │ ├── detailed_timer_component.rs │ ├── detailed_timer_component_state.rs │ ├── fuzzy_list.rs │ ├── general_layout_settings.rs │ ├── graph_component.rs │ ├── graph_component_state.rs │ ├── hotkey_config.rs │ ├── hotkey_system.rs │ ├── image_cache.rs │ ├── key_value_component_state.rs │ ├── layout.rs │ ├── layout_editor.rs │ ├── layout_editor_state.rs │ ├── layout_state.rs │ ├── lib.rs │ ├── linked_layout.rs │ ├── parse_run_result.rs │ ├── pb_chance_component.rs │ ├── possible_time_save_component.rs │ ├── potential_clean_up.rs │ ├── previous_segment_component.rs │ ├── run.rs │ ├── run_editor.rs │ ├── run_metadata.rs │ ├── run_metadata_custom_variable.rs │ ├── run_metadata_custom_variables_iter.rs │ ├── run_metadata_speedrun_com_variable.rs │ ├── run_metadata_speedrun_com_variables_iter.rs │ ├── segment.rs │ ├── segment_history.rs │ ├── segment_history_element.rs │ ├── segment_history_iter.rs │ ├── segment_time_component.rs │ ├── separator_component.rs │ ├── separator_component_state.rs │ ├── server_protocol.rs │ ├── setting_value.rs │ ├── shared_timer.rs │ ├── software_renderer.rs │ ├── splits_component.rs │ ├── splits_component_state.rs │ ├── sum_of_best_cleaner.rs │ ├── sum_of_best_component.rs │ ├── text_component.rs │ ├── text_component_state.rs │ ├── time.rs │ ├── time_span.rs │ ├── timer.rs │ ├── timer_component.rs │ ├── timer_component_state.rs │ ├── timer_read_lock.rs │ ├── timer_write_lock.rs │ ├── title_component.rs │ ├── title_component_state.rs │ ├── total_playtime_component.rs │ ├── web_command_sink.rs │ └── web_rendering.rs ├── crates ├── livesplit-auto-splitting │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ ├── process.rs │ │ ├── runtime │ │ │ ├── api │ │ │ │ ├── mod.rs │ │ │ │ ├── process.rs │ │ │ │ ├── runtime.rs │ │ │ │ ├── setting_value.rs │ │ │ │ ├── settings_list.rs │ │ │ │ ├── settings_map.rs │ │ │ │ ├── timer.rs │ │ │ │ ├── user_settings.rs │ │ │ │ └── wasi.rs │ │ │ └── mod.rs │ │ ├── settings │ │ │ ├── gui.rs │ │ │ ├── list.rs │ │ │ ├── map.rs │ │ │ ├── mod.rs │ │ │ └── value.rs │ │ ├── timer.rs │ │ └── wasi_path.rs │ └── tests │ │ ├── sandboxing.rs │ │ └── test-cases │ │ ├── create-file │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── empty │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── env │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── infinite-loop │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── proc-exit │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── random │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── segfault │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── sleep │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── stdout │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── threads │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ └── time │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── livesplit-hotkey │ ├── Cargo.toml │ └── src │ │ ├── hotkey.rs │ │ ├── key_code.rs │ │ ├── lib.rs │ │ ├── linux │ │ ├── evdev_impl.rs │ │ ├── mod.rs │ │ └── x11_impl.rs │ │ ├── macos │ │ ├── ax.rs │ │ ├── carbon.rs │ │ ├── cf.rs │ │ ├── cg.rs │ │ ├── mod.rs │ │ └── permission.rs │ │ ├── modifiers.rs │ │ ├── other │ │ └── mod.rs │ │ ├── wasm_web │ │ └── mod.rs │ │ └── windows │ │ └── mod.rs └── livesplit-title-abbreviations │ ├── Cargo.toml │ └── src │ └── lib.rs ├── src ├── analysis │ ├── current_pace.rs │ ├── delta.rs │ ├── mod.rs │ ├── pb_chance │ │ ├── mod.rs │ │ └── tests.rs │ ├── possible_time_save.rs │ ├── skill_curve.rs │ ├── state_helper.rs │ ├── sum_of_segments │ │ ├── best.rs │ │ ├── mod.rs │ │ ├── tests.rs │ │ └── worst.rs │ ├── tests │ │ ├── empty_run.rs │ │ ├── mod.rs │ │ └── semantic_colors.rs │ └── total_playtime.rs ├── auto_splitting │ └── mod.rs ├── comparison │ ├── average_segments.rs │ ├── balanced_pb.rs │ ├── best_segments.rs │ ├── best_split_times.rs │ ├── goal.rs │ ├── latest_run.rs │ ├── median_segments.rs │ ├── mod.rs │ ├── none.rs │ ├── tests │ │ ├── average.rs │ │ ├── balanced_pb.rs │ │ ├── empty.rs │ │ ├── median.rs │ │ └── mod.rs │ └── worst_segments.rs ├── component │ ├── blank_space.rs │ ├── current_comparison.rs │ ├── current_pace.rs │ ├── delta │ │ ├── mod.rs │ │ └── tests.rs │ ├── detailed_timer │ │ ├── mod.rs │ │ └── tests.rs │ ├── graph.rs │ ├── key_value.rs │ ├── mod.rs │ ├── pb_chance.rs │ ├── possible_time_save.rs │ ├── previous_segment.rs │ ├── segment_time │ │ ├── mod.rs │ │ └── tests.rs │ ├── separator.rs │ ├── splits │ │ ├── column.rs │ │ ├── mod.rs │ │ └── tests │ │ │ ├── column.rs │ │ │ └── mod.rs │ ├── sum_of_best.rs │ ├── text │ │ ├── mod.rs │ │ └── tests.rs │ ├── timer.rs │ ├── title │ │ ├── mod.rs │ │ └── tests.rs │ └── total_playtime.rs ├── event.rs ├── hotkey_config.rs ├── hotkey_system.rs ├── layout │ ├── component.rs │ ├── component_settings.rs │ ├── component_state.rs │ ├── editor │ │ ├── mod.rs │ │ └── state.rs │ ├── general_settings.rs │ ├── layout_direction.rs │ ├── layout_settings.rs │ ├── layout_state.rs │ ├── mod.rs │ └── parser │ │ ├── blank_space.rs │ │ ├── current_comparison.rs │ │ ├── current_pace.rs │ │ ├── delta.rs │ │ ├── detailed_timer.rs │ │ ├── font_resolving │ │ ├── gdi.rs │ │ ├── mod.rs │ │ └── name.rs │ │ ├── graph.rs │ │ ├── mod.rs │ │ ├── pb_chance.rs │ │ ├── possible_time_save.rs │ │ ├── previous_segment.rs │ │ ├── splits.rs │ │ ├── sum_of_best.rs │ │ ├── text.rs │ │ ├── timer.rs │ │ ├── title.rs │ │ └── total_playtime.rs ├── lib.rs ├── networking │ ├── mod.rs │ └── server_protocol.rs ├── platform │ ├── math.rs │ ├── mod.rs │ ├── no_std │ │ ├── mod.rs │ │ └── time.rs │ ├── normal │ │ └── mod.rs │ └── wasm │ │ ├── mod.rs │ │ ├── unknown │ │ ├── mod.rs │ │ └── time.rs │ │ └── web │ │ ├── mod.rs │ │ └── time.rs ├── rendering │ ├── component │ │ ├── blank_space.rs │ │ ├── detailed_timer.rs │ │ ├── graph.rs │ │ ├── key_value.rs │ │ ├── mod.rs │ │ ├── separator.rs │ │ ├── splits.rs │ │ ├── text.rs │ │ ├── timer.rs │ │ └── title.rs │ ├── consts.rs │ ├── default_text_engine │ │ ├── color_font │ │ │ ├── colr.rs │ │ │ ├── cpal.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── entity.rs │ ├── font │ │ ├── assets │ │ │ ├── FiraSans-Regular.ttf │ │ │ └── Timer.ttf │ │ ├── cache.rs │ │ └── mod.rs │ ├── icon.rs │ ├── mod.rs │ ├── resource │ │ ├── allocation.rs │ │ ├── handles.rs │ │ ├── mod.rs │ │ └── shared_ownership.rs │ ├── scene.rs │ ├── software.rs │ ├── svg.rs │ └── web │ │ ├── bindings.rs │ │ └── mod.rs ├── run │ ├── attempt.rs │ ├── comparisons.rs │ ├── editor │ │ ├── cleaning.rs │ │ ├── fuzzy_list.rs │ │ ├── mod.rs │ │ ├── segment_row.rs │ │ ├── state.rs │ │ └── tests │ │ │ ├── comparison.rs │ │ │ ├── custom_variables.rs │ │ │ ├── dissociate_run.rs │ │ │ ├── mark_as_modified.rs │ │ │ └── mod.rs │ ├── linked_layout.rs │ ├── mod.rs │ ├── parser │ │ ├── composite.rs │ │ ├── face_split.rs │ │ ├── flitter.rs │ │ ├── livesplit.rs │ │ ├── llanfair.rs │ │ ├── llanfair_gered.rs │ │ ├── mod.rs │ │ ├── opensplit.rs │ │ ├── portal2_live_timer.rs │ │ ├── shit_split.rs │ │ ├── source_live_timer.rs │ │ ├── speedrun_igt.rs │ │ ├── splitterino.rs │ │ ├── splitterz.rs │ │ ├── splitty.rs │ │ ├── time_split_tracker.rs │ │ ├── timer_kind.rs │ │ ├── urn.rs │ │ └── wsplit.rs │ ├── run_metadata.rs │ ├── saver │ │ ├── livesplit.rs │ │ ├── lss_image_header.bin │ │ └── mod.rs │ ├── segment.rs │ ├── segment_history.rs │ └── tests │ │ ├── comparison.rs │ │ ├── empty_run.rs │ │ ├── extended_category_name.rs │ │ ├── fixing.rs │ │ ├── linked_layout.rs │ │ ├── metadata.rs │ │ └── mod.rs ├── settings │ ├── alignment.rs │ ├── color.rs │ ├── field.rs │ ├── font.rs │ ├── gradient.rs │ ├── image │ │ ├── cache.rs │ │ ├── image_id.rs │ │ ├── mod.rs │ │ ├── shrinking.rs │ │ └── tests.rs │ ├── layout_background.rs │ ├── mod.rs │ ├── semantic_color.rs │ ├── settings_description.rs │ └── value.rs ├── timing │ ├── atomic_date_time.rs │ ├── formatter │ │ ├── accuracy.rs │ │ ├── complete.rs │ │ ├── days.rs │ │ ├── delta.rs │ │ ├── digits_format.rs │ │ ├── mod.rs │ │ ├── none_wrapper.rs │ │ ├── regular.rs │ │ ├── segment_time.rs │ │ └── timer.rs │ ├── mod.rs │ ├── time.rs │ ├── time_span.rs │ ├── time_stamp.rs │ ├── timer │ │ ├── active_attempt.rs │ │ ├── mod.rs │ │ └── tests │ │ │ ├── events.rs │ │ │ ├── mark_as_modified.rs │ │ │ ├── mod.rs │ │ │ └── variables.rs │ ├── timer_phase.rs │ └── timing_method.rs └── util │ ├── ascii_char.rs │ ├── ascii_set.rs │ ├── byte_parsing.rs │ ├── caseless.rs │ ├── clear_vec.rs │ ├── image.rs │ ├── mod.rs │ ├── not_nan.rs │ ├── ordered_map.rs │ ├── populate_string.rs │ ├── tests_helper.rs │ └── xml │ ├── helper.rs │ ├── mod.rs │ ├── reader.rs │ └── writer.rs └── tests ├── balanced_pb.rs ├── clean_sum_of_best.rs ├── escaped_lss.rs ├── layout_files ├── All.lsl ├── TextShadow.ls1l ├── WSplit.lsl ├── WithBackgroundImage.lsl ├── WithTimerDeltaBackground.lsl ├── custom_variable_ls1l.ls1l ├── custom_variable_splits.lsl ├── custom_variable_subsplits.lsl ├── dark.lsl ├── mod.rs └── subsplits.lsl ├── layout_parsing.rs ├── rendering.rs ├── run_files ├── 1734.timesplittracker ├── Celeste - Any% (1.2.1.5).lss ├── OpenSplit.osf ├── clean_sum_of_best.lss ├── flitter-small.scm ├── flitter.json ├── flitter.scm ├── livesplit1.0.lss ├── livesplit1.4.lss ├── livesplit1.5.lss ├── livesplit1.6.lss ├── livesplit1.6_gametime.lss ├── livesplit_attempt_ended_bug.lss ├── livesplit_fuzz_crash.lss ├── livesplit_fuzz_crash_utf8.lss ├── llanfair ├── llanfair_gered.lfs ├── llanfair_gered_icons.lfs ├── llanfair_gered_with_refs.lfs ├── mod.rs ├── portal2_live_timer1.csv ├── portal2_live_timer2.csv ├── source_live_timer.json ├── source_live_timer2.json ├── speedrun_igt.json ├── splitterino.splits ├── splitterz ├── timesplittracker.txt ├── urn.json └── wsplit └── split_parsing.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-wasip1] 2 | runner = "wasmtime run --dir ." 3 | 4 | [target.x86_64-pc-windows-msvc] 5 | linker = "rust-lld" 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | groups: 9 | wasmtime: 10 | patterns: 11 | - "wasmtime*" 12 | - "wasi-common" 13 | reviewers: 14 | - CryZe 15 | -------------------------------------------------------------------------------- /.github/workflows/before_deploy.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local tag=$(git tag --points-at HEAD) 5 | local src=$(pwd) \ 6 | stage= 7 | 8 | if [[ $OS_NAME =~ ^macos\-.*$ ]]; then 9 | stage=$(mktemp -d -t tmp) 10 | else 11 | stage=$(mktemp -d) 12 | fi 13 | 14 | (cd capi/bind_gen && cargo run) 15 | 16 | cp -r capi/bindings $stage/ 17 | cp target/$TARGET/max-opt/livesplit_core.dll $stage/livesplit_core.dll 2>/dev/null || : 18 | cp target/$TARGET/max-opt/livesplit_core.lib $stage/livesplit_core.lib 2>/dev/null || : 19 | cp target/$TARGET/max-opt/liblivesplit_core.a $stage/liblivesplit_core.a 2>/dev/null || : 20 | cp target/$TARGET/max-opt/liblivesplit_core.so $stage/liblivesplit_core.so 2>/dev/null || : 21 | cp target/$TARGET/max-opt/livesplit*.js* $stage/. 2>/dev/null || : 22 | cp target/$TARGET/max-opt/deps/*.wasm $stage/livesplit.wasm 2>/dev/null || : 23 | cp target/$TARGET/max-opt/liblivesplit_core.dylib $stage/liblivesplit_core.dylib 2>/dev/null || : 24 | 25 | cd $stage 26 | if [ "$OS_NAME" = "windows-latest" ]; then 27 | 7z a $src/livesplit-core-$tag-$TARGET.zip * 28 | else 29 | tar czf $src/livesplit-core-$tag-$TARGET.tar.gz * 30 | fi 31 | cd $src 32 | 33 | rm -rf $stage 34 | } 35 | 36 | main 37 | -------------------------------------------------------------------------------- /.github/workflows/build_shared.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local cargo=cross 5 | if [ "$SKIP_CROSS" = "skip" ]; then 6 | cargo=cargo 7 | fi 8 | local release_flag="" 9 | if [ "$IS_DEPLOY" = "true" ]; then 10 | release_flag="--profile max-opt" 11 | fi 12 | 13 | $cargo rustc -p livesplit-core-capi --crate-type cdylib --target $TARGET $release_flag $FEATURES 14 | } 15 | 16 | main 17 | -------------------------------------------------------------------------------- /.github/workflows/build_static.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local cargo=cross 5 | if [ "$SKIP_CROSS" = "skip" ]; then 6 | cargo=cargo 7 | fi 8 | local release_flag="" 9 | if [ "$IS_DEPLOY" = "true" ]; then 10 | release_flag="--profile max-opt" 11 | fi 12 | 13 | if [ "$NO_STD" = "true" ]; then 14 | cargo build --target $TARGET --no-default-features --features software-rendering $FEATURES 15 | return 16 | fi 17 | 18 | case $TARGET in 19 | wasm32-unknown-unknown) 20 | $cargo rustc -p livesplit-core-capi --crate-type cdylib --target $TARGET $release_flag $FEATURES 21 | ;; 22 | wasm32-wasip1) 23 | $cargo rustc -p livesplit-core-capi --crate-type cdylib --target $TARGET $release_flag $FEATURES 24 | ;; 25 | *) 26 | $cargo rustc -p livesplit-core-capi --crate-type staticlib --target $TARGET $release_flag $FEATURES 27 | ;; 28 | esac 29 | } 30 | 31 | main 32 | -------------------------------------------------------------------------------- /.github/workflows/test.sh: -------------------------------------------------------------------------------- 1 | set -ex 2 | 3 | main() { 4 | local cargo=cross 5 | 6 | # all features except those that sometimes should be skipped. 7 | local features="--features std,more-image-formats,image-shrinking,rendering,svg-rendering,default-text-engine,font-loading" 8 | 9 | if [ "$SKIP_CROSS" = "skip" ]; then 10 | cargo=cargo 11 | fi 12 | 13 | if [ "$SKIP_AUTO_SPLITTING" != "skip" ]; then 14 | features="$features,auto-splitting" 15 | fi 16 | 17 | # FIXME: Comment back in once we have API bindings again. 18 | # if [ "$SKIP_NETWORKING" != "skip" ]; then 19 | # features="$features,networking" 20 | # fi 21 | 22 | if [ "$SKIP_SOFTWARE_RENDERING" != "skip" ]; then 23 | features="$features,software-rendering" 24 | fi 25 | 26 | if [ "$TARGET" = "wasm32-wasip1" ]; then 27 | curl https://wasmtime.dev/install.sh -sSf | bash 28 | export PATH="$HOME/.wasmtime/bin:$PATH" 29 | else 30 | features="$features,wasm-web,web-rendering" 31 | fi 32 | 33 | $cargo test -p livesplit-core $features --target $TARGET 34 | $cargo test -p livesplit-core --no-default-features --features std --target $TARGET 35 | } 36 | 37 | main 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.rs.bk 2 | .DS_Store 3 | capi/bindings 4 | Cargo.lock 5 | docs 6 | target 7 | .vs* 8 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | # Temporary workaround because Rust 1.68+ requires more up to date containers. 2 | 3 | [target.i686-linux-android] 4 | image = "ghcr.io/cross-rs/i686-linux-android:main" 5 | [target.x86_64-linux-android] 6 | image = "ghcr.io/cross-rs/x86_64-linux-android:main" 7 | [target.arm-linux-androideabi] 8 | image = "ghcr.io/cross-rs/arm-linux-androideabi:main" 9 | [target.armv7-linux-androideabi] 10 | image = "ghcr.io/cross-rs/armv7-linux-androideabi:main" 11 | [target.thumbv7neon-linux-androideabi] 12 | image = "ghcr.io/cross-rs/thumbv7neon-linux-androideabi:main" 13 | [target.aarch64-linux-android] 14 | image = "ghcr.io/cross-rs/aarch64-linux-android:main" 15 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Christopher Serr, Sergey Papushin, Cris Hall-Ramos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benches/balanced_pb.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use livesplit_core::{ 4 | comparison::balanced_pb::BalancedPB, run::parser::livesplit, Run, Segment, TimeSpan, Timer, 5 | }; 6 | use std::fs; 7 | 8 | criterion_main!(benches); 9 | criterion_group!(benches, fake_splits, actual_splits); 10 | 11 | fn run_with_splits(timer: &mut Timer, splits: &[f64]) { 12 | timer.start().unwrap(); 13 | timer.initialize_game_time().unwrap(); 14 | timer.pause_game_time().unwrap(); 15 | 16 | for &split in splits { 17 | timer.set_game_time(TimeSpan::from_seconds(split)).unwrap(); 18 | timer.split().unwrap(); 19 | } 20 | 21 | timer.reset(true).unwrap(); 22 | } 23 | 24 | fn fake_splits(c: &mut Criterion) { 25 | let mut run = Run::new(); 26 | 27 | run.push_segment(Segment::new("First")); 28 | run.push_segment(Segment::new("Second")); 29 | run.push_segment(Segment::new("Third")); 30 | 31 | run.comparison_generators_mut().clear(); 32 | run.comparison_generators_mut().push(Box::new(BalancedPB)); 33 | 34 | let mut timer = Timer::new(run).unwrap(); 35 | 36 | run_with_splits(&mut timer, &[1.0, 2.0, 3.0]); 37 | run_with_splits(&mut timer, &[0.5, 2.5, 3.0]); 38 | run_with_splits(&mut timer, &[0.2, 2.8, 3.0]); 39 | 40 | let mut run = timer.into_run(false); 41 | 42 | c.bench_function("Balanced PB for synthetic splits", move |b| { 43 | b.iter(|| run.regenerate_comparisons()) 44 | }); 45 | } 46 | 47 | fn actual_splits(c: &mut Criterion) { 48 | let buf = fs::read_to_string("tests/run_files/livesplit1.6.lss").unwrap(); 49 | let mut run = livesplit::parse(&buf).unwrap(); 50 | run.comparison_generators_mut().clear(); 51 | run.comparison_generators_mut().push(Box::new(BalancedPB)); 52 | 53 | c.bench_function("Balanced PB for actual splits", move |b| { 54 | b.iter(|| run.regenerate_comparisons()) 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /benches/parsing.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | 3 | use livesplit_core::run::parser::livesplit; 4 | use std::fs; 5 | 6 | criterion_main!(benches); 7 | criterion_group!(benches, huge_game_icon, lots_of_icons, no_icons); 8 | 9 | fn huge_game_icon(c: &mut Criterion) { 10 | let buf = fs::read_to_string("tests/run_files/livesplit1.6_gametime.lss").unwrap(); 11 | 12 | c.bench_function("Parse With Huge Game Icon", move |b| { 13 | b.iter(|| livesplit::parse(&buf).unwrap()) 14 | }); 15 | } 16 | 17 | fn lots_of_icons(c: &mut Criterion) { 18 | let buf = fs::read_to_string("tests/run_files/Celeste - Any% (1.2.1.5).lss").unwrap(); 19 | 20 | c.bench_function("Parse with lots of Icons", move |b| { 21 | b.iter(|| livesplit::parse(&buf).unwrap()) 22 | }); 23 | } 24 | 25 | fn no_icons(c: &mut Criterion) { 26 | let buf = fs::read_to_string("tests/run_files/livesplit1.6.lss").unwrap(); 27 | 28 | c.bench_function("Parse without Icons", move |b| { 29 | b.iter(|| livesplit::parse(&buf).unwrap()) 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /capi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "livesplit-core-capi" 3 | version = "0.11.0" 4 | authors = ["Christopher Serr "] 5 | edition = "2024" 6 | publish = false 7 | 8 | [lib] 9 | name = "livesplit_core" 10 | crate-type = ["staticlib", "cdylib"] 11 | 12 | [dependencies] 13 | livesplit-core = { path = "..", default-features = false, features = ["std"] } 14 | serde_json = { version = "1.0.8", default-features = false } 15 | time = { version = "0.3.4", default-features = false, features = ["formatting"] } 16 | simdutf8 = { version = "0.1.5", default-features = false } 17 | 18 | wasm-bindgen = { version = "0.2.78", optional = true } 19 | wasm-bindgen-futures = { version = "0.4.28", optional = true } 20 | web-sys = { version = "0.3.28", optional = true } 21 | 22 | [features] 23 | default = ["image-shrinking"] 24 | image-shrinking = ["livesplit-core/image-shrinking"] 25 | software-rendering = ["livesplit-core/software-rendering"] 26 | wasm-web = ["livesplit-core/wasm-web", "wasm-bindgen", "wasm-bindgen-futures", "web-sys"] 27 | auto-splitting = ["livesplit-core/auto-splitting"] 28 | assume-str-parameters-are-utf8 = [] 29 | web-rendering = ["wasm-web", "livesplit-core/web-rendering"] 30 | -------------------------------------------------------------------------------- /capi/bind_gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Christopher Serr "] 3 | name = "bindings" 4 | version = "0.1.0" 5 | edition = "2024" 6 | publish = false 7 | 8 | [dependencies] 9 | heck = "0.5.0" 10 | clap = { version = "4.0.2", features = ["derive"] } 11 | syn = { version = "2.0.0", default-features = false, features = ["parsing", "full", "printing"] } 12 | -------------------------------------------------------------------------------- /capi/bind_gen/src/java/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::jni_cpp; 2 | use crate::{Class, Result}; 3 | use std::collections::BTreeMap; 4 | use std::fs::{create_dir_all, File}; 5 | use std::io::{BufWriter, Write}; 6 | use std::path::Path; 7 | 8 | mod jna; 9 | mod jni; 10 | 11 | pub fn write>(path: P, classes: &BTreeMap) -> Result<()> { 12 | let mut path = path.as_ref().to_owned(); 13 | 14 | path.push("jna"); 15 | create_dir_all(&path)?; 16 | jna::write(&path, classes)?; 17 | path.pop(); 18 | 19 | path.push("jni"); 20 | create_dir_all(&path)?; 21 | jni::write(&path, classes)?; 22 | path.pop(); 23 | 24 | path.push("LiveSplitCoreJNI.cpp"); 25 | jni_cpp::write(BufWriter::new(File::create(&path)?), classes)?; 26 | path.pop(); 27 | 28 | Ok(()) 29 | } 30 | 31 | fn write_class_comments(mut writer: W, comments: &[String]) -> Result<()> { 32 | write!( 33 | writer, 34 | r#" 35 | /**"# 36 | )?; 37 | 38 | for comment in comments { 39 | write!( 40 | writer, 41 | r#" 42 | * {}"#, 43 | comment 44 | .replace("", "null") 45 | .replace("", "true") 46 | .replace("", "false") 47 | )?; 48 | } 49 | 50 | write!( 51 | writer, 52 | r#" 53 | */"# 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /capi/bind_gen/src/kotlin/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{jni_cpp, Class, Result}; 2 | use std::{ 3 | collections::BTreeMap, 4 | fs::{create_dir_all, File}, 5 | io::BufWriter, 6 | path::Path, 7 | }; 8 | 9 | mod jni; 10 | 11 | pub fn write>(path: P, classes: &BTreeMap) -> Result<()> { 12 | let mut path = path.as_ref().to_owned(); 13 | 14 | path.push("jni"); 15 | create_dir_all(&path)?; 16 | jni::write(&path, classes)?; 17 | path.pop(); 18 | 19 | path.push("LiveSplitCoreJNI.cpp"); 20 | jni_cpp::write(BufWriter::new(File::create(&path)?), classes)?; 21 | path.pop(); 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /capi/bind_gen/src/swift/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Class; 2 | use std::{ 3 | collections::BTreeMap, 4 | fs::{self, File}, 5 | io::{BufWriter, Result}, 6 | path::Path, 7 | }; 8 | 9 | mod code; 10 | mod header; 11 | 12 | static MODULE_MAP: &str = include_str!("module.modulemap"); 13 | static LIVESPLIT_CORE_C: &str = "/* 14 | This file exists to make the Swift Package Manager recognize the folder as a 15 | C target. To compile this, you will need to add a folder containing the 16 | livesplit_core static library to the linker path. 17 | */ 18 | "; 19 | 20 | pub fn write>(path: P, classes: &BTreeMap) -> Result<()> { 21 | let mut path = path.as_ref().to_owned(); 22 | 23 | path.push("LiveSplitCore"); 24 | fs::create_dir_all(&path)?; 25 | 26 | path.push("LiveSplitCore.swift"); 27 | code::write(BufWriter::new(File::create(&path)?), classes)?; 28 | path.pop(); 29 | 30 | path.pop(); 31 | 32 | path.push("CLiveSplitCore"); 33 | fs::create_dir(&path)?; 34 | 35 | path.push("livesplit_core.c"); 36 | fs::write(&path, LIVESPLIT_CORE_C)?; 37 | path.pop(); 38 | 39 | path.push("include"); 40 | fs::create_dir(&path)?; 41 | 42 | path.push("livesplit_core.h"); 43 | header::write(BufWriter::new(File::create(&path)?), classes)?; 44 | path.pop(); 45 | 46 | path.push("module.modulemap"); 47 | fs::write(&path, MODULE_MAP) 48 | } 49 | -------------------------------------------------------------------------------- /capi/bind_gen/src/swift/module.modulemap: -------------------------------------------------------------------------------- 1 | module CLiveSplitCore [extern_c] { 2 | header "livesplit_core.h" 3 | link "livesplit_core" 4 | link framework "Carbon" 5 | link framework "CoreFoundation" 6 | link framework "CoreGraphics" 7 | export * 8 | } 9 | -------------------------------------------------------------------------------- /capi/bind_gen/src/typescript.rs: -------------------------------------------------------------------------------- 1 | pub static HEADER: &str = include_str!("typescript.ts"); 2 | -------------------------------------------------------------------------------- /capi/js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /capi/js/Makefile: -------------------------------------------------------------------------------- 1 | docs: bindings 2 | npm install 3 | npx typedoc --out ../../docs ../bindings/wasm/livesplit_core.ts 4 | 5 | bindings: 6 | @(cd ../bind_gen && cargo run) 7 | -------------------------------------------------------------------------------- /capi/js/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Christopher Serr" 4 | }, 5 | "bugs": { 6 | "url": "https://github.com/LiveSplit/livesplit-core/issues" 7 | }, 8 | "dependencies": {}, 9 | "description": "livesplit-core is a library that provides a lot of functionality for creating a speedrun timer.", 10 | "devDependencies": {}, 11 | "files": [ 12 | "README.md", 13 | "index.js", 14 | "livesplit_core.js", 15 | "index.d.ts" 16 | ], 17 | "homepage": "https://github.com/LiveSplit/livesplit-core#readme", 18 | "keywords": [ 19 | "speedrun", 20 | "timer", 21 | "livesplit" 22 | ], 23 | "license": "MIT", 24 | "main": "index.js", 25 | "maintainers": [ 26 | { 27 | "name": "cryze92", 28 | "email": "christopher.serr@gmail.com" 29 | } 30 | ], 31 | "name": "livesplit-core", 32 | "optionalDependencies": {}, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/LiveSplit/livesplit-core.git" 36 | }, 37 | "scripts": {}, 38 | "version": "0.10.2" 39 | } 40 | -------------------------------------------------------------------------------- /capi/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "exports.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "typescript": "^4.4.4" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^16.11.7", 16 | "typedoc": "^0.22.8" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /capi/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true 8 | }, 9 | "files": [ 10 | "../bindings/wasm/livesplit_core.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /capi/src/analysis.rs: -------------------------------------------------------------------------------- 1 | //! The analysis module provides a variety of functions for calculating 2 | //! information about runs. 3 | 4 | use super::time_span::{NullableOwnedTimeSpan, OwnedTimeSpan}; 5 | use livesplit_core::{ 6 | Run, Timer, TimingMethod, 7 | analysis::{sum_of_segments::calculate_best, total_playtime::calculate}, 8 | }; 9 | 10 | /// Calculates the Sum of Best Segments for the timing method provided. This is 11 | /// the fastest time possible to complete a run of a category, based on 12 | /// information collected from all the previous attempts. This often matches up 13 | /// with the sum of the best segment times of all the segments, but that may not 14 | /// always be the case, as skipped segments may introduce combined segments that 15 | /// may be faster than the actual sum of their best segment times. The name is 16 | /// therefore a bit misleading, but sticks around for historical reasons. You 17 | /// can choose to do a simple calculation instead, which excludes the Segment 18 | /// History from the calculation process. If there's an active attempt, you can 19 | /// choose to take it into account as well. Can return . 20 | #[unsafe(no_mangle)] 21 | pub extern "C" fn Analysis_calculate_sum_of_best( 22 | run: &Run, 23 | simple_calculation: bool, 24 | use_current_run: bool, 25 | method: TimingMethod, 26 | ) -> NullableOwnedTimeSpan { 27 | calculate_best(run.segments(), simple_calculation, use_current_run, method).map(Box::new) 28 | } 29 | 30 | /// Calculates the total playtime of the passed Run. 31 | #[unsafe(no_mangle)] 32 | pub extern "C" fn Analysis_calculate_total_playtime_for_run(run: &Run) -> OwnedTimeSpan { 33 | Box::new(calculate(run)) 34 | } 35 | 36 | /// Calculates the total playtime of the passed Timer. 37 | #[unsafe(no_mangle)] 38 | pub extern "C" fn Analysis_calculate_total_playtime_for_timer(timer: &Timer) -> OwnedTimeSpan { 39 | Box::new(calculate(timer)) 40 | } 41 | -------------------------------------------------------------------------------- /capi/src/atomic_date_time.rs: -------------------------------------------------------------------------------- 1 | //! An Atomic Date Time represents a UTC Date Time that tries to be as close to 2 | //! an atomic clock as possible. 3 | 4 | use crate::output_vec; 5 | use livesplit_core::AtomicDateTime; 6 | use std::os::raw::c_char; 7 | use time::format_description::well_known::Rfc3339; 8 | 9 | /// type 10 | pub type OwnedAtomicDateTime = Box; 11 | /// type 12 | pub type NullableOwnedAtomicDateTime = Option; 13 | 14 | /// drop 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn AtomicDateTime_drop(this: OwnedAtomicDateTime) { 17 | drop(this); 18 | } 19 | 20 | /// Represents whether the date time is actually properly derived from an 21 | /// atomic clock. If the synchronization with the atomic clock didn't happen 22 | /// yet or failed, this is set to . 23 | #[unsafe(no_mangle)] 24 | pub extern "C" fn AtomicDateTime_is_synchronized(this: &AtomicDateTime) -> bool { 25 | this.synced_with_atomic_clock 26 | } 27 | 28 | /// Converts this atomic date time into a RFC 3339 formatted date time. 29 | #[unsafe(no_mangle)] 30 | pub extern "C" fn AtomicDateTime_to_rfc3339(this: &AtomicDateTime) -> *const c_char { 31 | output_vec(|o| { 32 | let _ = this.time.format_into(o, &Rfc3339); 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /capi/src/attempt.rs: -------------------------------------------------------------------------------- 1 | //! An Attempt describes information about an attempt to run a specific category 2 | //! by a specific runner in the past. Every time a new attempt is started and 3 | //! then reset, an Attempt describing general information about it is created. 4 | 5 | use super::{output_time, output_time_span}; 6 | use crate::{atomic_date_time::NullableOwnedAtomicDateTime, time_span::NullableTimeSpan}; 7 | use livesplit_core::{Attempt, Time}; 8 | use std::ptr; 9 | 10 | /// type 11 | pub type OwnedAttempt = Box; 12 | 13 | /// Accesses the unique index of the attempt. This index is unique for the 14 | /// Run, not for all of them. 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn Attempt_index(this: &Attempt) -> i32 { 17 | this.index() 18 | } 19 | 20 | /// Accesses the split time of the last segment. If the attempt got reset 21 | /// early and didn't finish, this may be empty. 22 | #[unsafe(no_mangle)] 23 | pub extern "C" fn Attempt_time(this: &Attempt) -> *const Time { 24 | output_time(this.time()) 25 | } 26 | 27 | /// Accesses the amount of time the attempt has been paused for. If it is not 28 | /// known, this returns . This means that it may not necessarily be 29 | /// possible to differentiate whether a Run has not been paused or it simply 30 | /// wasn't stored. 31 | #[unsafe(no_mangle)] 32 | pub extern "C" fn Attempt_pause_time(this: &Attempt) -> *const NullableTimeSpan { 33 | if let Some(pause_time) = this.pause_time() { 34 | output_time_span(pause_time) 35 | } else { 36 | ptr::null() 37 | } 38 | } 39 | 40 | /// Accesses the point in time the attempt was started at. This returns 41 | /// if this information is not known. 42 | #[unsafe(no_mangle)] 43 | pub extern "C" fn Attempt_started(this: &Attempt) -> NullableOwnedAtomicDateTime { 44 | this.started().map(Box::new) 45 | } 46 | 47 | /// Accesses the point in time the attempt was ended at. This returns if 48 | /// this information is not known. 49 | #[unsafe(no_mangle)] 50 | pub extern "C" fn Attempt_ended(this: &Attempt) -> NullableOwnedAtomicDateTime { 51 | this.ended().map(Box::new) 52 | } 53 | -------------------------------------------------------------------------------- /capi/src/blank_space_component.rs: -------------------------------------------------------------------------------- 1 | //! The Blank Space Component is simply an empty component that doesn't show 2 | //! anything other than a background. It mostly serves as padding between other 3 | //! components. 4 | 5 | use super::{output_vec, Json}; 6 | use crate::blank_space_component_state::OwnedBlankSpaceComponentState; 7 | use crate::component::OwnedComponent; 8 | use livesplit_core::component::blank_space::Component as BlankSpaceComponent; 9 | 10 | /// type 11 | pub type OwnedBlankSpaceComponent = Box; 12 | 13 | /// Creates a new Blank Space Component. 14 | #[unsafe(no_mangle)] 15 | pub extern "C" fn BlankSpaceComponent_new() -> OwnedBlankSpaceComponent { 16 | Box::new(BlankSpaceComponent::new()) 17 | } 18 | 19 | /// drop 20 | #[unsafe(no_mangle)] 21 | pub extern "C" fn BlankSpaceComponent_drop(this: OwnedBlankSpaceComponent) { 22 | drop(this); 23 | } 24 | 25 | /// Converts the component into a generic component suitable for using with a 26 | /// layout. 27 | #[unsafe(no_mangle)] 28 | pub extern "C" fn BlankSpaceComponent_into_generic( 29 | this: OwnedBlankSpaceComponent, 30 | ) -> OwnedComponent { 31 | Box::new((*this).into()) 32 | } 33 | 34 | /// Encodes the component's state information as JSON. 35 | #[unsafe(no_mangle)] 36 | pub extern "C" fn BlankSpaceComponent_state_as_json(this: &mut BlankSpaceComponent) -> Json { 37 | output_vec(|o| { 38 | this.state().write_json(o).unwrap(); 39 | }) 40 | } 41 | 42 | /// Calculates the component's state. 43 | #[unsafe(no_mangle)] 44 | pub extern "C" fn BlankSpaceComponent_state( 45 | this: &mut BlankSpaceComponent, 46 | ) -> OwnedBlankSpaceComponentState { 47 | Box::new(this.state()) 48 | } 49 | -------------------------------------------------------------------------------- /capi/src/blank_space_component_state.rs: -------------------------------------------------------------------------------- 1 | //! The state object describes the information to visualize for this component. 2 | 3 | use livesplit_core::component::blank_space::State as BlankSpaceComponentState; 4 | 5 | /// type 6 | pub type OwnedBlankSpaceComponentState = Box; 7 | 8 | /// drop 9 | #[unsafe(no_mangle)] 10 | pub extern "C" fn BlankSpaceComponentState_drop(this: OwnedBlankSpaceComponentState) { 11 | drop(this); 12 | } 13 | 14 | /// The size of the component. 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn BlankSpaceComponentState_size(this: &BlankSpaceComponentState) -> u32 { 17 | this.size 18 | } 19 | -------------------------------------------------------------------------------- /capi/src/component.rs: -------------------------------------------------------------------------------- 1 | //! A Component provides information about a run in a way that is easy to 2 | //! visualize. This type can store any of the components provided by this crate. 3 | 4 | use livesplit_core::Component; 5 | 6 | /// type 7 | pub type OwnedComponent = Box; 8 | 9 | /// drop 10 | #[unsafe(no_mangle)] 11 | pub extern "C" fn Component_drop(this: OwnedComponent) { 12 | drop(this); 13 | } 14 | -------------------------------------------------------------------------------- /capi/src/current_comparison_component.rs: -------------------------------------------------------------------------------- 1 | //! The Current Comparison Component is a component that shows the name of the 2 | //! comparison that is currently selected to be compared against. 3 | 4 | use super::{output_vec, Json}; 5 | use crate::component::OwnedComponent; 6 | use crate::key_value_component_state::OwnedKeyValueComponentState; 7 | use livesplit_core::component::current_comparison::Component as CurrentComparisonComponent; 8 | use livesplit_core::Timer; 9 | 10 | /// type 11 | pub type OwnedCurrentComparisonComponent = Box; 12 | 13 | /// Creates a new Current Comparison Component. 14 | #[unsafe(no_mangle)] 15 | pub extern "C" fn CurrentComparisonComponent_new() -> OwnedCurrentComparisonComponent { 16 | Box::new(CurrentComparisonComponent::new()) 17 | } 18 | 19 | /// drop 20 | #[unsafe(no_mangle)] 21 | pub extern "C" fn CurrentComparisonComponent_drop(this: OwnedCurrentComparisonComponent) { 22 | drop(this); 23 | } 24 | 25 | /// Converts the component into a generic component suitable for using with a 26 | /// layout. 27 | #[unsafe(no_mangle)] 28 | pub extern "C" fn CurrentComparisonComponent_into_generic( 29 | this: OwnedCurrentComparisonComponent, 30 | ) -> OwnedComponent { 31 | Box::new((*this).into()) 32 | } 33 | 34 | /// Encodes the component's state information as JSON. 35 | #[unsafe(no_mangle)] 36 | pub extern "C" fn CurrentComparisonComponent_state_as_json( 37 | this: &mut CurrentComparisonComponent, 38 | timer: &Timer, 39 | ) -> Json { 40 | output_vec(|o| { 41 | this.state(timer).write_json(o).unwrap(); 42 | }) 43 | } 44 | 45 | /// Calculates the component's state based on the timer provided. 46 | #[unsafe(no_mangle)] 47 | pub extern "C" fn CurrentComparisonComponent_state( 48 | this: &mut CurrentComparisonComponent, 49 | timer: &Timer, 50 | ) -> OwnedKeyValueComponentState { 51 | Box::new(this.state(timer)) 52 | } 53 | -------------------------------------------------------------------------------- /capi/src/current_pace_component.rs: -------------------------------------------------------------------------------- 1 | //! The Current Pace Component is a component that shows a prediction of the 2 | //! current attempt's final time, if the current attempt's pace matches the 3 | //! chosen comparison for the remainder of the run. 4 | 5 | use super::{output_vec, Json}; 6 | use crate::component::OwnedComponent; 7 | use crate::key_value_component_state::OwnedKeyValueComponentState; 8 | use livesplit_core::component::current_pace::Component as CurrentPaceComponent; 9 | use livesplit_core::Timer; 10 | 11 | /// type 12 | pub type OwnedCurrentPaceComponent = Box; 13 | 14 | /// Creates a new Current Pace Component. 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn CurrentPaceComponent_new() -> OwnedCurrentPaceComponent { 17 | Box::new(CurrentPaceComponent::new()) 18 | } 19 | 20 | /// drop 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn CurrentPaceComponent_drop(this: OwnedCurrentPaceComponent) { 23 | drop(this); 24 | } 25 | 26 | /// Converts the component into a generic component suitable for using with a 27 | /// layout. 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn CurrentPaceComponent_into_generic( 30 | this: OwnedCurrentPaceComponent, 31 | ) -> OwnedComponent { 32 | Box::new((*this).into()) 33 | } 34 | 35 | /// Encodes the component's state information as JSON. 36 | #[unsafe(no_mangle)] 37 | pub extern "C" fn CurrentPaceComponent_state_as_json( 38 | this: &mut CurrentPaceComponent, 39 | timer: &Timer, 40 | ) -> Json { 41 | output_vec(|o| { 42 | this.state(&timer.snapshot()).write_json(o).unwrap(); 43 | }) 44 | } 45 | 46 | /// Calculates the component's state based on the timer provided. 47 | #[unsafe(no_mangle)] 48 | pub extern "C" fn CurrentPaceComponent_state( 49 | this: &mut CurrentPaceComponent, 50 | timer: &Timer, 51 | ) -> OwnedKeyValueComponentState { 52 | Box::new(this.state(&timer.snapshot())) 53 | } 54 | -------------------------------------------------------------------------------- /capi/src/delta_component.rs: -------------------------------------------------------------------------------- 1 | //! The Delta Component is a component that shows how far ahead or behind the 2 | //! current attempt is compared to the chosen comparison. 3 | 4 | use super::{output_vec, Json}; 5 | use crate::{component::OwnedComponent, key_value_component_state::OwnedKeyValueComponentState}; 6 | use livesplit_core::{component::delta::Component as DeltaComponent, GeneralLayoutSettings, Timer}; 7 | 8 | /// type 9 | pub type OwnedDeltaComponent = Box; 10 | 11 | /// Creates a new Delta Component. 12 | #[unsafe(no_mangle)] 13 | pub extern "C" fn DeltaComponent_new() -> OwnedDeltaComponent { 14 | Box::new(DeltaComponent::new()) 15 | } 16 | 17 | /// drop 18 | #[unsafe(no_mangle)] 19 | pub extern "C" fn DeltaComponent_drop(this: OwnedDeltaComponent) { 20 | drop(this); 21 | } 22 | 23 | /// Converts the component into a generic component suitable for using with a 24 | /// layout. 25 | #[unsafe(no_mangle)] 26 | pub extern "C" fn DeltaComponent_into_generic(this: OwnedDeltaComponent) -> OwnedComponent { 27 | Box::new((*this).into()) 28 | } 29 | 30 | /// Encodes the component's state information as JSON. 31 | #[unsafe(no_mangle)] 32 | pub extern "C" fn DeltaComponent_state_as_json( 33 | this: &mut DeltaComponent, 34 | timer: &Timer, 35 | layout_settings: &GeneralLayoutSettings, 36 | ) -> Json { 37 | output_vec(|o| { 38 | this.state(&timer.snapshot(), layout_settings) 39 | .write_json(o) 40 | .unwrap(); 41 | }) 42 | } 43 | 44 | /// Calculates the component's state based on the timer and the layout 45 | /// settings provided. 46 | #[unsafe(no_mangle)] 47 | pub extern "C" fn DeltaComponent_state( 48 | this: &mut DeltaComponent, 49 | timer: &Timer, 50 | layout_settings: &GeneralLayoutSettings, 51 | ) -> OwnedKeyValueComponentState { 52 | Box::new(this.state(&timer.snapshot(), layout_settings)) 53 | } 54 | -------------------------------------------------------------------------------- /capi/src/fuzzy_list.rs: -------------------------------------------------------------------------------- 1 | //! With a Fuzzy List, you can implement a fuzzy searching algorithm. The list 2 | //! stores all the items that can be searched for. With the `search` method you 3 | //! can then execute the actual fuzzy search which returns a list of all the 4 | //! elements found. This can be used to implement searching in a list of games. 5 | 6 | use super::{Json, output_vec, str}; 7 | use livesplit_core::run::editor::FuzzyList; 8 | use serde_json::to_writer; 9 | use std::os::raw::c_char; 10 | 11 | /// type 12 | pub type OwnedFuzzyList = Box; 13 | 14 | /// Creates a new Fuzzy List. 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn FuzzyList_new() -> OwnedFuzzyList { 17 | Box::new(FuzzyList::new()) 18 | } 19 | 20 | /// drop 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn FuzzyList_drop(this: OwnedFuzzyList) { 23 | drop(this); 24 | } 25 | 26 | /// Adds a new element to the list. 27 | #[unsafe(no_mangle)] 28 | pub unsafe extern "C" fn FuzzyList_push(this: &mut FuzzyList, text: *const c_char) { 29 | // SAFETY: The caller guarantees that `text` is valid. 30 | this.push(unsafe { str(text) }); 31 | } 32 | 33 | /// Searches for the pattern provided in the list. A list of all the 34 | /// matching elements is returned. The returned list has a maximum amount of 35 | /// elements provided to this method. 36 | #[unsafe(no_mangle)] 37 | pub unsafe extern "C" fn FuzzyList_search( 38 | this: &FuzzyList, 39 | pattern: *const c_char, 40 | max: usize, 41 | ) -> Json { 42 | // SAFETY: The caller guarantees that `pattern` is valid. 43 | output_vec(|o| { 44 | to_writer(o, &this.search(unsafe { str(pattern) }, max)).unwrap(); 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /capi/src/general_layout_settings.rs: -------------------------------------------------------------------------------- 1 | //! The general settings of the layout that apply to all components. 2 | 3 | use livesplit_core::GeneralLayoutSettings; 4 | 5 | /// type 6 | pub type OwnedGeneralLayoutSettings = Box; 7 | 8 | /// Creates a default general layout settings configuration. 9 | #[unsafe(no_mangle)] 10 | pub extern "C" fn GeneralLayoutSettings_default() -> OwnedGeneralLayoutSettings { 11 | Default::default() 12 | } 13 | 14 | /// drop 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn GeneralLayoutSettings_drop(this: OwnedGeneralLayoutSettings) { 17 | drop(this); 18 | } 19 | -------------------------------------------------------------------------------- /capi/src/graph_component.rs: -------------------------------------------------------------------------------- 1 | //! The Graph Component visualizes how far the current attempt has been ahead or 2 | //! behind the chosen comparison throughout the whole attempt. All the 3 | //! individual deltas are shown as points in a graph. 4 | 5 | use super::{output_vec, Json}; 6 | use crate::component::OwnedComponent; 7 | use crate::graph_component_state::OwnedGraphComponentState; 8 | use livesplit_core::component::graph::Component as GraphComponent; 9 | use livesplit_core::{GeneralLayoutSettings, Timer}; 10 | 11 | /// type 12 | pub type OwnedGraphComponent = Box; 13 | 14 | /// Creates a new Graph Component. 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn GraphComponent_new() -> OwnedGraphComponent { 17 | Box::new(GraphComponent::new()) 18 | } 19 | 20 | /// drop 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn GraphComponent_drop(this: OwnedGraphComponent) { 23 | drop(this); 24 | } 25 | 26 | /// Converts the component into a generic component suitable for using with a 27 | /// layout. 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn GraphComponent_into_generic(this: OwnedGraphComponent) -> OwnedComponent { 30 | Box::new((*this).into()) 31 | } 32 | 33 | /// Encodes the component's state information as JSON. 34 | #[unsafe(no_mangle)] 35 | pub extern "C" fn GraphComponent_state_as_json( 36 | this: &GraphComponent, 37 | timer: &Timer, 38 | layout_settings: &GeneralLayoutSettings, 39 | ) -> Json { 40 | output_vec(|o| { 41 | this.state(&timer.snapshot(), layout_settings) 42 | .write_json(o) 43 | .unwrap(); 44 | }) 45 | } 46 | 47 | /// Calculates the component's state based on the timer and layout settings 48 | /// provided. 49 | #[unsafe(no_mangle)] 50 | pub extern "C" fn GraphComponent_state( 51 | this: &GraphComponent, 52 | timer: &Timer, 53 | layout_settings: &GeneralLayoutSettings, 54 | ) -> OwnedGraphComponentState { 55 | Box::new(this.state(&timer.snapshot(), layout_settings)) 56 | } 57 | -------------------------------------------------------------------------------- /capi/src/key_value_component_state.rs: -------------------------------------------------------------------------------- 1 | //! The state object describes the information to visualize for a key value based component. 2 | 3 | use super::{output_str, output_vec}; 4 | use livesplit_core::component::key_value::State as KeyValueComponentState; 5 | use std::io::Write; 6 | use std::os::raw::c_char; 7 | 8 | /// type 9 | pub type OwnedKeyValueComponentState = Box; 10 | 11 | /// drop 12 | #[unsafe(no_mangle)] 13 | pub extern "C" fn KeyValueComponentState_drop(this: OwnedKeyValueComponentState) { 14 | drop(this); 15 | } 16 | 17 | /// The key to visualize. 18 | #[unsafe(no_mangle)] 19 | pub extern "C" fn KeyValueComponentState_key(this: &KeyValueComponentState) -> *const c_char { 20 | output_str(&this.key) 21 | } 22 | 23 | /// The value to visualize. 24 | #[unsafe(no_mangle)] 25 | pub extern "C" fn KeyValueComponentState_value(this: &KeyValueComponentState) -> *const c_char { 26 | output_str(&this.value) 27 | } 28 | 29 | /// The semantic coloring information the value carries. 30 | #[unsafe(no_mangle)] 31 | pub extern "C" fn KeyValueComponentState_semantic_color( 32 | this: &KeyValueComponentState, 33 | ) -> *const c_char { 34 | output_vec(|f| write!(f, "{:?}", this.semantic_color).unwrap()) 35 | } 36 | -------------------------------------------------------------------------------- /capi/src/linked_layout.rs: -------------------------------------------------------------------------------- 1 | //! A Linked Layout associates a Layout with a Run. If the Run has a Linked 2 | //! Layout, it is supposed to be visualized with the Layout that is linked with 3 | //! it. 4 | 5 | use super::{output_str, str}; 6 | use livesplit_core::run::LinkedLayout; 7 | use std::os::raw::c_char; 8 | 9 | /// type 10 | pub type OwnedLinkedLayout = Box; 11 | /// type 12 | pub type NullableOwnedLinkedLayout = Option; 13 | 14 | /// Creates a new Linked Layout with the path specified. If the path is empty, 15 | /// the default layout is used instead. 16 | #[unsafe(no_mangle)] 17 | pub unsafe extern "C" fn LinkedLayout_new(path: *const c_char) -> OwnedLinkedLayout { 18 | // SAFETY: The caller guarantees that `path` is valid. 19 | let path = unsafe { str(path) }; 20 | Box::new(if path.is_empty() { 21 | LinkedLayout::Default 22 | } else { 23 | LinkedLayout::Path(path.to_owned()) 24 | }) 25 | } 26 | 27 | /// drop 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn LinkedLayout_drop(this: OwnedLinkedLayout) { 30 | drop(this); 31 | } 32 | 33 | /// Checks whether the linked layout is the default layout. 34 | #[unsafe(no_mangle)] 35 | pub extern "C" fn LinkedLayout_is_default(this: &LinkedLayout) -> bool { 36 | matches!(this, LinkedLayout::Default) 37 | } 38 | 39 | /// Returns the path of the linked layout, if it's not the default layout. 40 | #[unsafe(no_mangle)] 41 | pub extern "C" fn LinkedLayout_path(this: &LinkedLayout) -> *const c_char { 42 | output_str(match this { 43 | LinkedLayout::Path(path) => path, 44 | _ => "", 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /capi/src/parse_run_result.rs: -------------------------------------------------------------------------------- 1 | //! A run parsed by the Composite Parser. This contains the Run itself and 2 | //! information about which parser parsed it. 3 | 4 | use super::output_vec; 5 | use crate::run::OwnedRun; 6 | use livesplit_core::run::parser::{composite::ParsedRun, TimerKind}; 7 | use std::{io::Write, os::raw::c_char}; 8 | 9 | /// type 10 | pub type ParseRunResult = Option>; 11 | /// type 12 | pub type OwnedParseRunResult = Box; 13 | 14 | /// drop 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn ParseRunResult_drop(this: OwnedParseRunResult) { 17 | drop(this); 18 | } 19 | 20 | /// Returns if the Run got parsed successfully. is returned otherwise. 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn ParseRunResult_parsed_successfully(this: &ParseRunResult) -> bool { 23 | this.is_some() 24 | } 25 | 26 | /// Moves the actual Run object out of the Result. You may not call this if the 27 | /// Run wasn't parsed successfully. 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn ParseRunResult_unwrap(this: OwnedParseRunResult) -> OwnedRun { 30 | Box::new((*this).unwrap().run) 31 | } 32 | 33 | /// Accesses the name of the Parser that parsed the Run. You may not call this 34 | /// if the Run wasn't parsed successfully. 35 | #[unsafe(no_mangle)] 36 | pub extern "C" fn ParseRunResult_timer_kind(this: &ParseRunResult) -> *const c_char { 37 | output_vec(|f| write!(f, "{}", this.as_ref().unwrap().kind).unwrap()) 38 | } 39 | 40 | /// Checks whether the Parser parsed a generic timer. Since a generic timer can 41 | /// have any name, it may clash with the specific timer formats that 42 | /// livesplit-core supports. With this function you can determine if a generic 43 | /// timer format was parsed, instead of one of the more specific timer formats. 44 | #[unsafe(no_mangle)] 45 | pub extern "C" fn ParseRunResult_is_generic_timer(this: &ParseRunResult) -> bool { 46 | matches!( 47 | this, 48 | Some(ParsedRun { 49 | kind: TimerKind::Generic(_), 50 | .. 51 | }) 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /capi/src/pb_chance_component.rs: -------------------------------------------------------------------------------- 1 | //! The PB Chance Component is a component that shows how likely it is to beat 2 | //! the Personal Best. If there is no active attempt it shows the general chance 3 | //! of beating the Personal Best. During an attempt it actively changes based on 4 | //! how well the attempt is going. 5 | 6 | use super::{output_vec, Json}; 7 | use crate::component::OwnedComponent; 8 | use crate::key_value_component_state::OwnedKeyValueComponentState; 9 | use livesplit_core::component::pb_chance::Component as PbChanceComponent; 10 | use livesplit_core::Timer; 11 | 12 | /// type 13 | pub type OwnedPbChanceComponent = Box; 14 | 15 | /// Creates a new PB Chance Component. 16 | #[unsafe(no_mangle)] 17 | pub extern "C" fn PbChanceComponent_new() -> OwnedPbChanceComponent { 18 | Box::new(PbChanceComponent::new()) 19 | } 20 | 21 | /// drop 22 | #[unsafe(no_mangle)] 23 | pub extern "C" fn PbChanceComponent_drop(this: OwnedPbChanceComponent) { 24 | drop(this); 25 | } 26 | 27 | /// Converts the component into a generic component suitable for using with a 28 | /// layout. 29 | #[unsafe(no_mangle)] 30 | pub extern "C" fn PbChanceComponent_into_generic(this: OwnedPbChanceComponent) -> OwnedComponent { 31 | Box::new((*this).into()) 32 | } 33 | 34 | /// Encodes the component's state information as JSON. 35 | #[unsafe(no_mangle)] 36 | pub extern "C" fn PbChanceComponent_state_as_json(this: &PbChanceComponent, timer: &Timer) -> Json { 37 | output_vec(|o| { 38 | this.state(&timer.snapshot()).write_json(o).unwrap(); 39 | }) 40 | } 41 | 42 | /// Calculates the component's state based on the timer provided. 43 | #[unsafe(no_mangle)] 44 | pub extern "C" fn PbChanceComponent_state( 45 | this: &PbChanceComponent, 46 | timer: &Timer, 47 | ) -> OwnedKeyValueComponentState { 48 | Box::new(this.state(&timer.snapshot())) 49 | } 50 | -------------------------------------------------------------------------------- /capi/src/possible_time_save_component.rs: -------------------------------------------------------------------------------- 1 | //! The Possible Time Save Component is a component that shows how much time the 2 | //! chosen comparison could've saved for the current segment, based on the Best 3 | //! Segments. This component also allows showing the Total Possible Time Save 4 | //! for the remainder of the current attempt. 5 | 6 | use super::{output_vec, Json}; 7 | use crate::component::OwnedComponent; 8 | use crate::key_value_component_state::OwnedKeyValueComponentState; 9 | use livesplit_core::component::possible_time_save::Component as PossibleTimeSaveComponent; 10 | use livesplit_core::Timer; 11 | 12 | /// type 13 | pub type OwnedPossibleTimeSaveComponent = Box; 14 | 15 | /// Creates a new Possible Time Save Component. 16 | #[unsafe(no_mangle)] 17 | pub extern "C" fn PossibleTimeSaveComponent_new() -> OwnedPossibleTimeSaveComponent { 18 | Box::new(PossibleTimeSaveComponent::new()) 19 | } 20 | 21 | /// drop 22 | #[unsafe(no_mangle)] 23 | pub extern "C" fn PossibleTimeSaveComponent_drop(this: OwnedPossibleTimeSaveComponent) { 24 | drop(this); 25 | } 26 | 27 | /// Converts the component into a generic component suitable for using with a 28 | /// layout. 29 | #[unsafe(no_mangle)] 30 | pub extern "C" fn PossibleTimeSaveComponent_into_generic( 31 | this: OwnedPossibleTimeSaveComponent, 32 | ) -> OwnedComponent { 33 | Box::new((*this).into()) 34 | } 35 | 36 | /// Encodes the component's state information as JSON. 37 | #[unsafe(no_mangle)] 38 | pub extern "C" fn PossibleTimeSaveComponent_state_as_json( 39 | this: &PossibleTimeSaveComponent, 40 | timer: &Timer, 41 | ) -> Json { 42 | output_vec(|o| { 43 | this.state(&timer.snapshot()).write_json(o).unwrap(); 44 | }) 45 | } 46 | 47 | /// Calculates the component's state based on the timer provided. 48 | #[unsafe(no_mangle)] 49 | pub extern "C" fn PossibleTimeSaveComponent_state( 50 | this: &PossibleTimeSaveComponent, 51 | timer: &Timer, 52 | ) -> OwnedKeyValueComponentState { 53 | Box::new(this.state(&timer.snapshot())) 54 | } 55 | -------------------------------------------------------------------------------- /capi/src/potential_clean_up.rs: -------------------------------------------------------------------------------- 1 | //! Describes a potential clean up that could be applied. You can query a 2 | //! message describing the details of this potential clean up. A potential clean 3 | //! up can then be turned into an actual clean up in order to apply it to the 4 | //! Run. 5 | 6 | use super::output_vec; 7 | use livesplit_core::run::editor::cleaning::PotentialCleanUp; 8 | use std::{io::Write, os::raw::c_char}; 9 | 10 | /// type 11 | pub type OwnedPotentialCleanUp = Box>; 12 | /// type 13 | pub type NullableOwnedPotentialCleanUp = Option; 14 | 15 | /// drop 16 | #[unsafe(no_mangle)] 17 | pub extern "C" fn PotentialCleanUp_drop(this: OwnedPotentialCleanUp) { 18 | drop(this); 19 | } 20 | 21 | /// Accesses the message describing the potential clean up that can be applied 22 | /// to a Run. 23 | #[unsafe(no_mangle)] 24 | pub extern "C" fn PotentialCleanUp_message(this: &PotentialCleanUp<'static>) -> *const c_char { 25 | output_vec(|s| write!(s, "{this}").unwrap()) 26 | } 27 | -------------------------------------------------------------------------------- /capi/src/run_metadata_custom_variable.rs: -------------------------------------------------------------------------------- 1 | //! A custom variable is a key value pair storing additional information about a 2 | //! run. Unlike the speedrun.com variables, these can be fully custom and don't 3 | //! need to correspond to anything on speedrun.com. Permanent custom variables 4 | //! can be specified by the runner. Additionally auto splitters or other sources 5 | //! may provide temporary custom variables that are not stored in the splits 6 | //! files. 7 | 8 | use super::output_str; 9 | use livesplit_core::run::CustomVariable; 10 | use std::os::raw::c_char; 11 | 12 | /// type 13 | pub type RunMetadataCustomVariable = (*const str, *const CustomVariable); 14 | /// type 15 | pub type NullableRunMetadataCustomVariable = RunMetadataCustomVariable; 16 | /// type 17 | pub type OwnedRunMetadataCustomVariable = Box; 18 | 19 | /// drop 20 | #[unsafe(no_mangle)] 21 | pub extern "C" fn RunMetadataCustomVariable_drop(this: OwnedRunMetadataCustomVariable) { 22 | drop(this); 23 | } 24 | 25 | /// Accesses the name of this custom variable. 26 | #[unsafe(no_mangle)] 27 | pub unsafe extern "C" fn RunMetadataCustomVariable_name( 28 | this: &RunMetadataCustomVariable, 29 | ) -> *const c_char { 30 | // SAFETY: The caller guarantees that `this` is valid. 31 | unsafe { output_str(&*this.0) } 32 | } 33 | 34 | /// Accesses the value of this custom variable. 35 | #[unsafe(no_mangle)] 36 | pub unsafe extern "C" fn RunMetadataCustomVariable_value( 37 | this: &RunMetadataCustomVariable, 38 | ) -> *const c_char { 39 | // SAFETY: The caller guarantees that `this` is valid. 40 | unsafe { output_str(&(*this.1).value) } 41 | } 42 | 43 | /// Returns if the custom variable is permanent. Permanent variables get 44 | /// stored in the splits file and are visible in the run editor. Temporary 45 | /// variables are not. 46 | #[unsafe(no_mangle)] 47 | pub unsafe extern "C" fn RunMetadataCustomVariable_is_permanent( 48 | this: &RunMetadataCustomVariable, 49 | ) -> bool { 50 | // SAFETY: The caller guarantees that `this` is valid. 51 | unsafe { (*this.1).is_permanent } 52 | } 53 | -------------------------------------------------------------------------------- /capi/src/run_metadata_custom_variables_iter.rs: -------------------------------------------------------------------------------- 1 | //! An iterator iterating over all the custom variables and their values 2 | //! that have been specified. 3 | 4 | use super::RUN_METADATA_CUSTOM_VARIABLE; 5 | use crate::run_metadata_custom_variable::{ 6 | NullableRunMetadataCustomVariable, RunMetadataCustomVariable, 7 | }; 8 | use livesplit_core::{run::CustomVariable, util::ordered_map}; 9 | use std::ptr; 10 | 11 | /// type 12 | pub type RunMetadataCustomVariablesIter = ordered_map::Iter<'static, CustomVariable>; 13 | /// type 14 | pub type OwnedRunMetadataCustomVariablesIter = Box; 15 | 16 | /// drop 17 | #[unsafe(no_mangle)] 18 | pub extern "C" fn RunMetadataCustomVariablesIter_drop(this: OwnedRunMetadataCustomVariablesIter) { 19 | drop(this); 20 | } 21 | 22 | /// Accesses the next custom variable. Returns if there are no more 23 | /// variables. 24 | #[unsafe(no_mangle)] 25 | pub extern "C" fn RunMetadataCustomVariablesIter_next( 26 | this: &mut RunMetadataCustomVariablesIter, 27 | ) -> *const NullableRunMetadataCustomVariable { 28 | if let Some((name, value)) = this.next() { 29 | RUN_METADATA_CUSTOM_VARIABLE.with(|output| { 30 | output.set((name, value)); 31 | output.as_ptr() as *const RunMetadataCustomVariable 32 | }) 33 | } else { 34 | ptr::null() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /capi/src/run_metadata_speedrun_com_variable.rs: -------------------------------------------------------------------------------- 1 | //! A speedrun.com variable is an arbitrary key value pair storing additional 2 | //! information about the category. An example of this may be whether Amiibos 3 | //! are used in the category. 4 | 5 | use super::output_str; 6 | use std::os::raw::c_char; 7 | 8 | /// type 9 | pub type RunMetadataSpeedrunComVariable = (*const str, *const String); 10 | /// type 11 | pub type NullableRunMetadataSpeedrunComVariable = RunMetadataSpeedrunComVariable; 12 | /// type 13 | pub type OwnedRunMetadataSpeedrunComVariable = Box; 14 | 15 | /// drop 16 | #[unsafe(no_mangle)] 17 | pub extern "C" fn RunMetadataSpeedrunComVariable_drop(this: OwnedRunMetadataSpeedrunComVariable) { 18 | drop(this); 19 | } 20 | 21 | /// Accesses the name of this speedrun.com variable. 22 | #[unsafe(no_mangle)] 23 | pub unsafe extern "C" fn RunMetadataSpeedrunComVariable_name( 24 | this: &RunMetadataSpeedrunComVariable, 25 | ) -> *const c_char { 26 | // SAFETY: The caller guarantees that `this` is valid. 27 | unsafe { output_str(&*this.0) } 28 | } 29 | 30 | /// Accesses the value of this speedrun.com variable. 31 | #[unsafe(no_mangle)] 32 | pub unsafe extern "C" fn RunMetadataSpeedrunComVariable_value( 33 | this: &RunMetadataSpeedrunComVariable, 34 | ) -> *const c_char { 35 | // SAFETY: The caller guarantees that `this` is valid. 36 | unsafe { output_str(&*this.1) } 37 | } 38 | -------------------------------------------------------------------------------- /capi/src/run_metadata_speedrun_com_variables_iter.rs: -------------------------------------------------------------------------------- 1 | //! An iterator iterating over all the speedrun.com variables and their values 2 | //! that have been specified. 3 | 4 | use super::RUN_METADATA_SPEEDRUN_COM_VARIABLE; 5 | use crate::run_metadata_speedrun_com_variable::{ 6 | NullableRunMetadataSpeedrunComVariable, RunMetadataSpeedrunComVariable, 7 | }; 8 | use livesplit_core::util::ordered_map; 9 | use std::ptr; 10 | 11 | /// type 12 | pub type RunMetadataSpeedrunComVariablesIter = ordered_map::Iter<'static, String>; 13 | /// type 14 | pub type OwnedRunMetadataSpeedrunComVariablesIter = Box; 15 | 16 | /// drop 17 | #[unsafe(no_mangle)] 18 | pub extern "C" fn RunMetadataSpeedrunComVariablesIter_drop( 19 | this: OwnedRunMetadataSpeedrunComVariablesIter, 20 | ) { 21 | drop(this); 22 | } 23 | 24 | /// Accesses the next speedrun.com variable. Returns if there are no more 25 | /// variables. 26 | #[unsafe(no_mangle)] 27 | pub extern "C" fn RunMetadataSpeedrunComVariablesIter_next( 28 | this: &mut RunMetadataSpeedrunComVariablesIter, 29 | ) -> *const NullableRunMetadataSpeedrunComVariable { 30 | if let Some((name, value)) = this.next() { 31 | RUN_METADATA_SPEEDRUN_COM_VARIABLE.with(|output| { 32 | output.set((name, value)); 33 | output.as_ptr() as *const RunMetadataSpeedrunComVariable 34 | }) 35 | } else { 36 | ptr::null() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /capi/src/segment_history.rs: -------------------------------------------------------------------------------- 1 | //! Stores the segment times achieved for a certain segment. Each segment is 2 | //! tagged with an index. Only segment times with an index larger than 0 are 3 | //! considered times actually achieved by the runner, while the others are 4 | //! artifacts of route changes and similar algorithmic changes. 5 | 6 | use crate::segment_history_iter::OwnedSegmentHistoryIter; 7 | use livesplit_core::SegmentHistory; 8 | 9 | /// type 10 | pub type OwnedSegmentHistory = Box; 11 | 12 | /// Iterates over all the segment times and their indices. 13 | #[unsafe(no_mangle)] 14 | pub extern "C" fn SegmentHistory_iter(this: &'static SegmentHistory) -> OwnedSegmentHistoryIter { 15 | Box::new(this.iter()) 16 | } 17 | -------------------------------------------------------------------------------- /capi/src/segment_history_element.rs: -------------------------------------------------------------------------------- 1 | //! A segment time achieved for a segment. It is tagged with an index. Only 2 | //! segment times with an index larger than 0 are considered times actually 3 | //! achieved by the runner, while the others are artifacts of route changes and 4 | //! similar algorithmic changes. 5 | 6 | use super::output_time; 7 | use livesplit_core::Time; 8 | 9 | /// type 10 | pub type SegmentHistoryElement = (i32, Time); 11 | /// type 12 | pub type NullableSegmentHistoryElement = SegmentHistoryElement; 13 | /// type 14 | pub type OwnedSegmentHistoryElement = Box; 15 | 16 | /// Accesses the index of the segment history element. 17 | #[unsafe(no_mangle)] 18 | pub extern "C" fn SegmentHistoryElement_index(this: &SegmentHistoryElement) -> i32 { 19 | this.0 20 | } 21 | 22 | /// Accesses the segment time of the segment history element. 23 | #[unsafe(no_mangle)] 24 | pub extern "C" fn SegmentHistoryElement_time(this: &SegmentHistoryElement) -> *const Time { 25 | output_time(this.1) 26 | } 27 | -------------------------------------------------------------------------------- /capi/src/segment_history_iter.rs: -------------------------------------------------------------------------------- 1 | //! Iterates over all the segment times of a segment and their indices. 2 | 3 | use super::SEGMENT_HISTORY_ELEMENT; 4 | use crate::segment_history_element::{NullableSegmentHistoryElement, SegmentHistoryElement}; 5 | use livesplit_core::Time; 6 | use std::{ptr, slice}; 7 | 8 | /// type 9 | pub type SegmentHistoryIter = slice::Iter<'static, (i32, Time)>; 10 | /// type 11 | pub type OwnedSegmentHistoryIter = Box; 12 | 13 | /// drop 14 | #[unsafe(no_mangle)] 15 | pub extern "C" fn SegmentHistoryIter_drop(this: OwnedSegmentHistoryIter) { 16 | drop(this); 17 | } 18 | 19 | /// Accesses the next Segment History element. Returns if there are no 20 | /// more elements. 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn SegmentHistoryIter_next( 23 | this: &mut SegmentHistoryIter, 24 | ) -> *const NullableSegmentHistoryElement { 25 | if let Some(&element) = this.next() { 26 | SEGMENT_HISTORY_ELEMENT.with(|output| { 27 | output.set(element); 28 | output.as_ptr() as *const SegmentHistoryElement 29 | }) 30 | } else { 31 | ptr::null() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /capi/src/segment_time_component.rs: -------------------------------------------------------------------------------- 1 | //! The Segment Time Component is a component that shows the time for the current 2 | //! segment in a comparison of your choosing. If no comparison is specified it 3 | //! uses the timer's current comparison. 4 | 5 | use super::{output_vec, Json}; 6 | use crate::component::OwnedComponent; 7 | use crate::key_value_component_state::OwnedKeyValueComponentState; 8 | use livesplit_core::component::segment_time::Component as SegmentTimeComponent; 9 | use livesplit_core::Timer; 10 | 11 | /// type 12 | pub type OwnedSegmentTimeComponent = Box; 13 | 14 | /// Creates a new Segment Time Component. 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn SegmentTimeComponent_new() -> OwnedSegmentTimeComponent { 17 | Box::new(SegmentTimeComponent::new()) 18 | } 19 | 20 | /// drop 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn SegmentTimeComponent_drop(this: OwnedSegmentTimeComponent) { 23 | drop(this); 24 | } 25 | 26 | /// Converts the component into a generic component suitable for using with a 27 | /// layout. 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn SegmentTimeComponent_into_generic( 30 | this: OwnedSegmentTimeComponent, 31 | ) -> OwnedComponent { 32 | Box::new((*this).into()) 33 | } 34 | 35 | /// Encodes the component's state information as JSON. 36 | #[unsafe(no_mangle)] 37 | pub extern "C" fn SegmentTimeComponent_state_as_json( 38 | this: &SegmentTimeComponent, 39 | timer: &Timer, 40 | ) -> Json { 41 | output_vec(|o| { 42 | this.state(timer).write_json(o).unwrap(); 43 | }) 44 | } 45 | 46 | /// Calculates the component's state based on the timer provided. 47 | #[unsafe(no_mangle)] 48 | pub extern "C" fn SegmentTimeComponent_state( 49 | this: &SegmentTimeComponent, 50 | timer: &Timer, 51 | ) -> OwnedKeyValueComponentState { 52 | Box::new(this.state(timer)) 53 | } 54 | -------------------------------------------------------------------------------- /capi/src/separator_component.rs: -------------------------------------------------------------------------------- 1 | //! The Separator Component is a simple component that only serves to render 2 | //! separators between components. 3 | 4 | use crate::component::OwnedComponent; 5 | use crate::separator_component_state::OwnedSeparatorComponentState; 6 | use livesplit_core::component::separator::Component as SeparatorComponent; 7 | 8 | /// type 9 | pub type OwnedSeparatorComponent = Box; 10 | 11 | /// Creates a new Separator Component. 12 | #[unsafe(no_mangle)] 13 | pub extern "C" fn SeparatorComponent_new() -> OwnedSeparatorComponent { 14 | Box::new(SeparatorComponent::new()) 15 | } 16 | 17 | /// drop 18 | #[unsafe(no_mangle)] 19 | pub extern "C" fn SeparatorComponent_drop(this: OwnedSeparatorComponent) { 20 | drop(this); 21 | } 22 | 23 | /// Converts the component into a generic component suitable for using with a 24 | /// layout. 25 | #[unsafe(no_mangle)] 26 | pub extern "C" fn SeparatorComponent_into_generic(this: OwnedSeparatorComponent) -> OwnedComponent { 27 | Box::new((*this).into()) 28 | } 29 | 30 | /// Calculates the component's state. 31 | #[unsafe(no_mangle)] 32 | pub extern "C" fn SeparatorComponent_state( 33 | this: &mut SeparatorComponent, 34 | ) -> OwnedSeparatorComponentState { 35 | Box::new(this.state()) 36 | } 37 | -------------------------------------------------------------------------------- /capi/src/separator_component_state.rs: -------------------------------------------------------------------------------- 1 | //! The state object describes the information to visualize for this component. 2 | 3 | use livesplit_core::component::separator::State as SeparatorComponentState; 4 | 5 | /// type 6 | pub type OwnedSeparatorComponentState = Box; 7 | 8 | /// drop 9 | #[unsafe(no_mangle)] 10 | pub extern "C" fn SeparatorComponentState_drop(this: OwnedSeparatorComponentState) { 11 | drop(this); 12 | } 13 | -------------------------------------------------------------------------------- /capi/src/server_protocol.rs: -------------------------------------------------------------------------------- 1 | //! The server protocol is an experimental JSON based protocol that is used to 2 | //! remotely control the timer. Every command that you send has a response in 3 | //! the form of a JSON object indicating whether the command was successful or 4 | //! not. 5 | 6 | use livesplit_core::networking::server_protocol; 7 | use wasm_bindgen::prelude::*; 8 | 9 | use crate::command_sink::CommandSink; 10 | 11 | /// The server protocol is an experimental JSON based protocol that is used to 12 | /// remotely control the timer. Every command that you send has a response in 13 | /// the form of a JSON object indicating whether the command was successful or 14 | /// not. 15 | #[wasm_bindgen] 16 | pub struct ServerProtocol {} 17 | 18 | #[wasm_bindgen] 19 | impl ServerProtocol { 20 | /// Handles an incoming command and returns the response to be sent. 21 | pub async unsafe fn handleCommand(command: &str, commandSink: *const CommandSink) -> String { 22 | // SAFETY: The caller must ensure that the pointer is valid. 23 | server_protocol::handle_command(command, unsafe { &*commandSink }).await 24 | } 25 | 26 | /// Encodes an event that happened to be sent. 27 | pub fn encodeEvent(event: u32) -> Option { 28 | Some(server_protocol::encode_event(event.try_into().ok()?)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /capi/src/shared_timer.rs: -------------------------------------------------------------------------------- 1 | //! A Shared Timer that can be used to share a single timer object with multiple 2 | //! owners. 3 | 4 | use crate::timer::OwnedTimer; 5 | use crate::timer_read_lock::OwnedTimerReadLock; 6 | use crate::timer_write_lock::OwnedTimerWriteLock; 7 | use livesplit_core::SharedTimer; 8 | 9 | /// type 10 | pub type OwnedSharedTimer = Box; 11 | 12 | /// Creates a new shared timer handle that shares the same timer. The inner 13 | /// timer object only gets disposed when the final handle gets disposed. 14 | #[unsafe(no_mangle)] 15 | pub extern "C" fn SharedTimer_share(this: &SharedTimer) -> OwnedSharedTimer { 16 | Box::new(this.clone()) 17 | } 18 | 19 | /// drop 20 | #[unsafe(no_mangle)] 21 | pub extern "C" fn SharedTimer_drop(this: OwnedSharedTimer) { 22 | drop(this); 23 | } 24 | 25 | /// Requests read access to the timer that is being shared. This blocks the 26 | /// thread as long as there is an active write lock. Dispose the read lock when 27 | /// you are done using the timer. 28 | #[unsafe(no_mangle)] 29 | pub extern "C" fn SharedTimer_read(this: &'static SharedTimer) -> OwnedTimerReadLock { 30 | Box::new(this.read().unwrap()) 31 | } 32 | 33 | /// Requests write access to the timer that is being shared. This blocks the 34 | /// thread as long as there are active write or read locks. Dispose the write 35 | /// lock when you are done using the timer. 36 | #[unsafe(no_mangle)] 37 | pub extern "C" fn SharedTimer_write(this: &'static SharedTimer) -> OwnedTimerWriteLock { 38 | Box::new(this.write().unwrap()) 39 | } 40 | 41 | /// Replaces the timer that is being shared by the timer provided. This blocks 42 | /// the thread as long as there are active write or read locks. Everyone who is 43 | /// sharing the old timer will share the provided timer after successful 44 | /// completion. 45 | #[unsafe(no_mangle)] 46 | pub extern "C" fn SharedTimer_replace_inner(this: &SharedTimer, timer: OwnedTimer) { 47 | *this.write().unwrap() = *timer; 48 | } 49 | -------------------------------------------------------------------------------- /capi/src/sum_of_best_cleaner.rs: -------------------------------------------------------------------------------- 1 | //! A Sum of Best Cleaner allows you to interactively remove potential issues in 2 | //! the Segment History that lead to an inaccurate Sum of Best. If you skip a 3 | //! split, whenever you get to the next split, the combined segment time might 4 | //! be faster than the sum of the individual best segments. The Sum of Best 5 | //! Cleaner will point out all of occurrences of this and allows you to delete 6 | //! them individually if any of them seem wrong. 7 | 8 | use crate::potential_clean_up::{NullableOwnedPotentialCleanUp, OwnedPotentialCleanUp}; 9 | use livesplit_core::run::editor::cleaning::SumOfBestCleaner; 10 | 11 | /// type 12 | pub type OwnedSumOfBestCleaner = Box>; 13 | 14 | /// drop 15 | #[unsafe(no_mangle)] 16 | pub extern "C" fn SumOfBestCleaner_drop(this: OwnedSumOfBestCleaner) { 17 | drop(this); 18 | } 19 | 20 | /// Returns the next potential clean up. If there are no more potential 21 | /// clean ups, is returned. 22 | #[unsafe(no_mangle)] 23 | pub extern "C" fn SumOfBestCleaner_next_potential_clean_up( 24 | this: &'static mut SumOfBestCleaner<'static>, 25 | ) -> NullableOwnedPotentialCleanUp { 26 | this.next_potential_clean_up().map(Box::new) 27 | } 28 | 29 | /// Applies a clean up to the Run. 30 | #[unsafe(no_mangle)] 31 | pub extern "C" fn SumOfBestCleaner_apply( 32 | this: &'static mut SumOfBestCleaner<'static>, 33 | clean_up: OwnedPotentialCleanUp, 34 | ) { 35 | this.apply((*clean_up).into()); 36 | } 37 | -------------------------------------------------------------------------------- /capi/src/sum_of_best_component.rs: -------------------------------------------------------------------------------- 1 | //! The Sum of Best Segments Component shows the fastest possible time to 2 | //! complete a run of this category, based on information collected from all the 3 | //! previous attempts. This often matches up with the sum of the best segment 4 | //! times of all the segments, but that may not always be the case, as skipped 5 | //! segments may introduce combined segments that may be faster than the actual 6 | //! sum of their best segment times. The name is therefore a bit misleading, but 7 | //! sticks around for historical reasons. 8 | 9 | use super::{output_vec, Json}; 10 | use crate::component::OwnedComponent; 11 | use crate::key_value_component_state::OwnedKeyValueComponentState; 12 | use livesplit_core::component::sum_of_best::Component as SumOfBestComponent; 13 | use livesplit_core::Timer; 14 | 15 | /// type 16 | pub type OwnedSumOfBestComponent = Box; 17 | 18 | /// Creates a new Sum of Best Segments Component. 19 | #[unsafe(no_mangle)] 20 | pub extern "C" fn SumOfBestComponent_new() -> OwnedSumOfBestComponent { 21 | Box::new(SumOfBestComponent::new()) 22 | } 23 | 24 | /// drop 25 | #[unsafe(no_mangle)] 26 | pub extern "C" fn SumOfBestComponent_drop(this: OwnedSumOfBestComponent) { 27 | drop(this); 28 | } 29 | 30 | /// Converts the component into a generic component suitable for using with a 31 | /// layout. 32 | #[unsafe(no_mangle)] 33 | pub extern "C" fn SumOfBestComponent_into_generic(this: OwnedSumOfBestComponent) -> OwnedComponent { 34 | Box::new((*this).into()) 35 | } 36 | 37 | /// Encodes the component's state information as JSON. 38 | #[unsafe(no_mangle)] 39 | pub extern "C" fn SumOfBestComponent_state_as_json( 40 | this: &SumOfBestComponent, 41 | timer: &Timer, 42 | ) -> Json { 43 | output_vec(|o| { 44 | this.state(timer).write_json(o).unwrap(); 45 | }) 46 | } 47 | 48 | /// Calculates the component's state based on the timer provided. 49 | #[unsafe(no_mangle)] 50 | pub extern "C" fn SumOfBestComponent_state( 51 | this: &SumOfBestComponent, 52 | timer: &Timer, 53 | ) -> OwnedKeyValueComponentState { 54 | Box::new(this.state(timer)) 55 | } 56 | -------------------------------------------------------------------------------- /capi/src/text_component_state.rs: -------------------------------------------------------------------------------- 1 | //! The state object describes the information to visualize for this component. 2 | 3 | use super::output_str; 4 | use livesplit_core::component::text::{State as TextComponentState, TextState}; 5 | use std::os::raw::c_char; 6 | 7 | /// type 8 | pub type OwnedTextComponentState = Box; 9 | 10 | /// drop 11 | #[unsafe(no_mangle)] 12 | pub extern "C" fn TextComponentState_drop(this: OwnedTextComponentState) { 13 | drop(this); 14 | } 15 | 16 | /// Accesses the left part of the text. If the text isn't split up, an empty 17 | /// string is returned instead. 18 | #[unsafe(no_mangle)] 19 | pub extern "C" fn TextComponentState_left(this: &TextComponentState) -> *const c_char { 20 | if let TextState::Split(left, _) = &this.text { 21 | output_str(left) 22 | } else { 23 | output_str("") 24 | } 25 | } 26 | 27 | /// Accesses the right part of the text. If the text isn't split up, an empty 28 | /// string is returned instead. 29 | #[unsafe(no_mangle)] 30 | pub extern "C" fn TextComponentState_right(this: &TextComponentState) -> *const c_char { 31 | if let TextState::Split(_, right) = &this.text { 32 | output_str(right) 33 | } else { 34 | output_str("") 35 | } 36 | } 37 | 38 | /// Accesses the centered text. If the text isn't centered, an empty string is 39 | /// returned instead. 40 | #[unsafe(no_mangle)] 41 | pub extern "C" fn TextComponentState_center(this: &TextComponentState) -> *const c_char { 42 | if let TextState::Center(center) = &this.text { 43 | output_str(center) 44 | } else { 45 | output_str("") 46 | } 47 | } 48 | 49 | /// Returns whether the text is split up into a left and right part. 50 | #[unsafe(no_mangle)] 51 | pub extern "C" fn TextComponentState_is_split(this: &TextComponentState) -> bool { 52 | matches!(this.text, TextState::Split(_, _)) 53 | } 54 | -------------------------------------------------------------------------------- /capi/src/time.rs: -------------------------------------------------------------------------------- 1 | //! A time that can store a Real Time and a Game Time. Both of them are 2 | //! optional. 3 | 4 | use crate::time_span::NullableTimeSpan; 5 | use livesplit_core::{Time, TimingMethod}; 6 | use std::ptr; 7 | 8 | /// type 9 | pub type OwnedTime = Box