Repository: LiveSplit/livesplit-core
Pull request #270 (2019-11-19 13:50:56)

Implement initial support for no_std
This implements initial support for no_std. Modules such as parsing, saving and rendering are disabled and timing has to be manually provided by registering a global handler. The rendering module is on its way to no_std support as well, but not all Pull Requests to the necessary dependencies are done yet.

See #223 for more information.


Patch:
diff --git a/.github/workflows/build_shared.sh b/.github/workflows/build_shared.sh
index f236d60..77dbe2c 100644
--- a/.github/workflows/build_shared.sh
+++ b/.github/workflows/build_shared.sh
@@ -10,7 +10,7 @@ main() {
         release_flag="--release"
     fi
 
-    $cargo build -p cdylib --target $TARGET $release_flag
+    $cargo build -p cdylib --target $TARGET $release_flag $FEATURES
 }
 
 main
diff --git a/.github/workflows/build_static.sh b/.github/workflows/build_static.sh
index b80d780..9171b53 100644
--- a/.github/workflows/build_static.sh
+++ b/.github/workflows/build_static.sh
@@ -10,20 +10,25 @@ main() {
         release_flag="--release"
     fi
 
+    if [ "$NO_STD" = "true" ]; then
+        (cd crates/no-std-test && cargo build --target $TARGET $FEATURES)
+        return
+    fi
+
     case $TARGET in
         asmjs-unknown-emscripten)
-            $cargo build -p livesplit --target $TARGET --release
+            $cargo build -p livesplit --target $TARGET --release $FEATURES
             ;;
         wasm32-unknown-emscripten)
             rm target/wasm32-unknown-emscripten/release/deps/*.wasm 2>/dev/null || :
             rm target/wasm32-unknown-emscripten/release/deps/*.js 2>/dev/null || :
-            $cargo build -p livesplit --target $TARGET --release
+            $cargo build -p livesplit --target $TARGET --release $FEATURES
             ;;
         wasm32-unknown-unknown)
-            $cargo build -p cdylib --target $TARGET --release
+            $cargo build -p cdylib --target $TARGET --release $FEATURES
             ;;
         *)
-            $cargo build -p staticlib --target $TARGET $release_flag
+            $cargo build -p staticlib --target $TARGET $release_flag $FEATURES
             ;;
     esac
 }
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index c5ba4e8..2362cd1 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -15,10 +15,25 @@ jobs:
       fail-fast: false
       matrix:
         label:
+          # Bare Metal
+          - Bare Metal Nvidia PTX 64
+          - Bare Metal ARM Cortex-M thumbv6m
+          - Bare Metal ARM Cortex-M thumbv7em
+          - Bare Metal ARM Cortex-M thumbv7em Hardware Float
+          - Bare Metal ARM Cortex-M thumbv7m
+          - Bare Metal ARM Cortex-M thumbv8m.base
+          - Bare Metal ARM Cortex-M thumbv8m.main
+          - Bare Metal ARM Cortex-M thumbv8m.main Hardware Float
+          - Bare Metal RISC-V 32 i
+          - Bare Metal RISC-V 32 imac
+          - Bare Metal RISC-V 32 imc
+          - Bare Metal RISC-V 64 gc
+          - Bare Metal RISC-V 64 imac
+
           # Web
-          # FIXME: asmjs is broken at the moment. It's unclear why.
+          # FIXME: emscripten is broken at the moment. cross' emscripten is too old.
           # - Web asmjs
-          - Web wasm32 emscripten
+          # - Web wasm32 emscripten
           - Web wasm32
 
           # Windows
@@ -104,6 +119,140 @@ jobs:
           - macOS Nightly
 
         include:
+          # Bare Metal
+          - label: Bare Metal Nvidia PTX 64
+            target: nvptx64-nvidia-cuda
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal ARM Cortex-M thumbv6m
+            target: thumbv6m-none-eabi
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+            features: "--features doesnt-have-atomics"
+
+          - label: Bare Metal ARM Cortex-M thumbv7em
+            target: thumbv7em-none-eabi
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal ARM Cortex-M thumbv7em Hardware Float
+            target: thumbv7em-none-eabihf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal ARM Cortex-M thumbv7m
+            target: thumbv7m-none-eabi
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal ARM Cortex-M thumbv8m.base
+            target: thumbv8m.base-none-eabi
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal ARM Cortex-M thumbv8m.main
+            target: thumbv8m.main-none-eabi
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal ARM Cortex-M thumbv8m.main Hardware Float
+            target: thumbv8m.main-none-eabihf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal RISC-V 32 i
+            target: riscv32i-unknown-none-elf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+            features: "--features doesnt-have-atomics"
+
+          - label: Bare Metal RISC-V 32 imac
+            target: riscv32imac-unknown-none-elf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal RISC-V 32 imc
+            target: riscv32imc-unknown-none-elf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+            features: "--features doesnt-have-atomics"
+
+          - label: Bare Metal RISC-V 64 gc
+            target: riscv64gc-unknown-none-elf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
+          - label: Bare Metal RISC-V 64 imac
+            target: riscv64imac-unknown-none-elf
+            os: ubuntu-latest
+            toolchain: stable
+            tests: skip
+            dylib: skip
+            release: skip
+            no_std: true
+            install_target: true
+
           # Web
           # - label: Web asmjs
           #   target: asmjs-unknown-emscripten
@@ -113,13 +262,12 @@ jobs:
           #   dylib: skip
           #   release_anyway: true
 
-          - label: Web wasm32 emscripten
-            target: wasm32-unknown-emscripten
-            os: ubuntu-latest
-            toolchain: stable
-            tests: skip
-            dylib: skip
-            release_anyway: true
+          # - label: Web wasm32 emscripten
+          #   target: wasm32-unknown-emscripten
+          #   os: ubuntu-latest
+          #   toolchain: stable
+          #   tests: skip
+          #   dylib: skip
 
           - label: Web wasm32
             target: wasm32-unknown-unknown
@@ -127,7 +275,6 @@ jobs:
             tests: skip
             dylib: skip
             install_target: true
-            release_anyway: true
 
           # Windows
           - label: Windows aarch64
@@ -394,17 +541,20 @@ jobs:
             target: x86_64-pc-windows-msvc
             os: windows-latest
             toolchain: beta
+            release: skip
             cross: skip
 
           - label: Windows Nightly
             target: x86_64-pc-windows-msvc
             os: windows-latest
             toolchain: nightly
+            release: skip
             cross: skip
 
           - label: Windows Beta gnu
             target: x86_64-pc-windows-gnu
             toolchain: beta-x86_64-pc-windows-gnu
+            release: skip
             os: windows-latest
             cross: skip
             install_target: true
@@ -412,6 +562,7 @@ jobs:
           - label: Windows Nightly gnu
             target: x86_64-pc-windows-gnu
             toolchain: nightly-x86_64-pc-windows-gnu
+            release: skip
             os: windows-latest
             cross: skip
             install_target: true
@@ -420,22 +571,26 @@ jobs:
             target: x86_64-unknown-linux-gnu
             os: ubuntu-latest
             toolchain: beta
+            release: skip
 
           - label: Linux Nightly
             target: x86_64-unknown-linux-gnu
             os: ubuntu-latest
             toolchain: nightly
+            release: skip
 
           - label: macOS Beta
             target: x86_64-apple-darwin
             os: macOS-latest
             toolchain: beta
+            release: skip
             cross: skip
 
           - label: macOS Nightly
             target: x86_64-apple-darwin
             os: macOS-latest
             toolchain: nightly
+            release: skip
             cross: skip
 
     steps:
@@ -463,6 +618,8 @@ jobs:
           TARGET: ${{ matrix.target }}
           SKIP_CROSS: ${{ matrix.cross }}
           IS_DEPLOY: ${{ startsWith(github.ref, 'refs/tags/') && (matrix.release_anyway != '' || !(startsWith(matrix.toolchain, 'nightly') || startsWith(matrix.toolchain, 'beta'))) }}
+          FEATURES: ${{ matrix.features }}
+          NO_STD: ${{ matrix.no_std }}
 
       - name: Build Shared Library
         if: matrix.dylib == ''
@@ -471,6 +628,7 @@ jobs:
           TARGET: ${{ matrix.target }}
           SKIP_CROSS: ${{ matrix.cross }}
           IS_DEPLOY: ${{ startsWith(github.ref, 'refs/tags/') && (matrix.release_anyway != '' || !(startsWith(matrix.toolchain, 'nightly') || startsWith(matrix.toolchain, 'beta'))) }}
+          FEATURES: ${{ matrix.features }}
 
       - name: Test
         if: matrix.tests == ''
@@ -480,7 +638,7 @@ jobs:
           SKIP_CROSS: ${{ matrix.cross }}
 
       - name: Prepare Release
-        if: startsWith(github.ref, 'refs/tags/') && (matrix.release_anyway != '' || !(startsWith(matrix.toolchain, 'nightly') || startsWith(matrix.toolchain, 'beta')))
+        if: startsWith(github.ref, 'refs/tags/') && matrix.release == ''
         shell: bash
         run: .github/workflows/before_deploy.sh
         env:
@@ -488,7 +646,7 @@ jobs:
           TARGET: ${{ matrix.target }}
 
       - name: Release
-        if: startsWith(github.ref, 'refs/tags/') && (matrix.release_anyway != '' || !(startsWith(matrix.toolchain, 'nightly') || startsWith(matrix.toolchain, 'beta')))
+        if: startsWith(github.ref, 'refs/tags/') && matrix.release == ''
         uses: softprops/action-gh-release@v1
         with:
           files: livesplit-core-*.*
diff --git a/Cargo.toml b/Cargo.toml
index 80664cb..3a8f1f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,29 +32,32 @@ panic = "abort"
 
 [workspace]
 members = ["capi", "capi/bind_gen", "capi/js", "capi/staticlib", "capi/cdylib", "crates/*"]
+exclude = ["crates/no-std-test"]
 
 [dependencies]
-# Required crates
+# core
 base64 = { version = "0.11.0", default-features = false, features = ["alloc"] }
-cfg-if = "0.1.7"
-chrono = { version = "0.4.0", features = ["serde", "clock"], default-features = false }
+cfg-if = "0.1.10"
 derive_more = { version = "0.99.1", default-features = false, features = ["not", "add", "from", "deref"] }
+hashbrown = "0.6.3"
+libm = "0.1.4"
+livesplit-hotkey = { path = "crates/livesplit-hotkey", version = "0.5.0", default-features = false }
 odds = { version = "0.3.1", default-features = false }
 ordered-float = { version = "1.0.2", default-features = false }
-palette = { version = "0.5.0", default-features = false, features = ["std"] }
-serde = { version = "1.0.98", default-features = false, features = ["derive", "alloc", "std"] }
-snafu = { version = "0.6.0", default-features = false, features = ["std"] }
+palette = { version = "0.5.0", default-features = false, features = ["libm"] }
+serde = { version = "1.0.98", default-features = false, features = ["derive", "alloc"] }
+snafu = { version = "0.6.0", default-features = false }
 unicase = "2.6.0"
 
 # std
-byteorder = "1.3.2"
-image = { version = "0.22.0", features = ["png_codec"], default-features = false }
-indexmap = { version = "1.2.0", default-features = false, features = ["serde-1"] }
-livesplit-hotkey = { path = "crates/livesplit-hotkey", version = "0.5.0" }
-parking_lot = { version = "0.9.0", default-features = false }
-quick-xml = { version = "0.17.0", default-features = false }
-serde_json = "1.0.8"
-utf-8 = "0.7.4"
+byteorder = { version = "1.3.2", optional = true }
+chrono = { version = "0.4.0", features = ["serde", "clock"], default-features = false, optional = true }
+image = { version = "0.22.0", features = ["png_codec"], default-features = false, optional = true }
+indexmap = { version = "1.2.0", default-features = false, features = ["serde-1"], optional = true }
+parking_lot = { version = "0.9.0", default-features = false, optional = true }
+quick-xml = { version = "0.17.0", default-features = false, optional = true }
+serde_json = { version = "1.0.8", optional = true }
+utf-8 = { version = "0.7.4", optional = true }
 
 # Rendering
 euclid = { version = "0.20.0", default-features = false, optional = true }
@@ -73,12 +76,24 @@ criterion = "0.3.0"
 crc = "1.8.1"
 
 [features]
-default = ["image-shrinking"]
+default = ["image-shrinking", "std"]
+doesnt-have-atomics = []
+std = ["byteorder", "chrono", "image", "indexmap", "livesplit-hotkey/std", "palette/std", "parking_lot", "quick-xml", "serde_json", "serde/std", "snafu/std", "utf-8"]
 more-image-formats = ["image/webp", "image/pnm", "image/ico", "image/jpeg", "image/gif_codec", "image/tiff", "image/tga", "image/bmp", "image/hdr"]
 image-shrinking = ["more-image-formats"]
-rendering = ["more-image-formats", "euclid", "livesplit-title-abbreviations", "lyon", "rusttype", "smallvec"]
+rendering = ["std", "more-image-formats", "euclid", "livesplit-title-abbreviations", "lyon", "rusttype", "smallvec"]
 software-rendering = ["rendering", "euc", "vek", "derive_more/mul"]
 
+# FIXME: Some targets don't have atomics, but we can't test for this properly
+# yet. So there's a feature you explicitly have to opt into to deactivate the
+# usage of atomics. Vice versa would be too dangerous, as `default-features =
+# false` would deactivate atomics then. However there's also `cargo test
+# --all-features`, which is equally dangerous. To detect this, we have an
+# additional `internal-use-all-features` feature that is only ever activated
+# when `--all-features` is passed, so we can ignore the `doesnt-have-atomics` in
+# that case. https://github.com/rust-lang/rust/issues/32976
+internal-use-all-features = []
+
 [[bench]]
 name = "balanced_pb"
 harness = false
diff --git a/capi/Cargo.toml b/capi/Cargo.toml
index 99a3109..9f3759a 100644
--- a/capi/Cargo.toml
+++ b/capi/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Christopher Serr <christopher.serr@gmail.com>"]
 edition = "2018"
 
 [dependencies]
-livesplit-core = { path = "..", default-features = false }
+livesplit-core = { path = "..", default-features = false, features = ["std"] }
 serde_json = "1.0.8"
 
 [features]
diff --git a/crates/livesplit-hotkey/Cargo.toml b/crates/livesplit-hotkey/Cargo.toml
index b9eeaca..8692303 100644
--- a/crates/livesplit-hotkey/Cargo.toml
+++ b/crates/livesplit-hotkey/Cargo.toml
@@ -14,18 +14,23 @@ winapi = { version = "0.3.2", features = [
     "libloaderapi",
     "processthreadsapi",
     "winuser"
-] }
-parking_lot = "0.9.0"
+], optional = true }
+parking_lot = { version = "0.9.0", optional = true }
 
 [target.'cfg(target_os = "linux")'.dependencies]
-x11-dl = "2.18.3"
-mio = "0.6.16"
-promising-future = "0.2.4"
+x11-dl = { version = "2.18.3", optional = true }
+mio = { version = "0.6.16", optional = true }
+promising-future = { version = "0.2.4", optional = true }
 
 [target.'cfg(target_os = "emscripten")'.dependencies]
-stdweb = "0.3.0"
-parking_lot = "0.9.0"
+stdweb = { version = "0.3.0", optional = true }
+parking_lot = { version = "0.9.0", optional = true }
 
 [dependencies]
-snafu = { version = "0.6.0", default-features = false, features = ["std"] }
-serde = { version = "1.0.98", default-features = false, features = ["derive", "alloc", "std"] }
+cfg-if = "0.1.10"
+snafu = { version = "0.6.0", default-features = false }
+serde = { version = "1.0.98", default-features = false, features = ["derive", "alloc"] }
+
+[features]
+default = ["std"]
+std = ["snafu/std", "serde/std", "stdweb", "parking_lot", "x11-dl", "mio", "promising-future", "winapi", "parking_lot"]
diff --git a/crates/livesplit-hotkey/src/lib.rs b/crates/livesplit-hotkey/src/lib.rs
index 84a3e71..eac5dd1 100644
--- a/crates/livesplit-hotkey/src/lib.rs
+++ b/crates/livesplit-hotkey/src/lib.rs
@@ -1,40 +1,27 @@
 // For js! macro.
 #![recursion_limit = "1024"]
+#![cfg_attr(not(feature = "std"), no_std)]
 
-#[cfg(windows)]
-pub mod windows;
-#[cfg(windows)]
-pub use crate::windows::*;
-
-#[cfg(target_os = "linux")]
-pub mod linux;
-#[cfg(target_os = "linux")]
-pub use crate::linux::*;
-
-#[cfg(target_os = "emscripten")]
-pub mod emscripten;
-#[cfg(target_os = "emscripten")]
-pub use crate::emscripten::*;
-#[cfg(target_os = "emscripten")]
-#[macro_use]
-extern crate stdweb;
-
-#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
-pub mod wasm;
-#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
-pub use crate::wasm::*;
-
-#[cfg(not(any(
-    windows,
-    target_os = "linux",
-    target_os = "emscripten",
-    all(target_arch = "wasm32", target_os = "unknown")
-)))]
-pub mod other;
-#[cfg(not(any(
-    windows,
-    target_os = "linux",
-    target_os = "emscripten",
-    all(target_arch = "wasm32", target_os = "unknown")
-)))]
-pub use crate::other::*;
+cfg_if::cfg_if! {
+    if #[cfg(not(feature = "std"))] {
+        pub mod other;
+        pub use crate::other::*;
+    } else if #[cfg(windows)] {
+        pub mod windows;
+        pub use crate::windows::*;
+    } else if #[cfg(target_os = "linux")] {
+        pub mod linux;
+        pub use crate::linux::*;
+    } else if #[cfg(target_os = "emscripten")] {
+        pub mod emscripten;
+        pub use crate::emscripten::*;
+        #[macro_use]
+        extern crate stdweb;
+    } else if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
+        pub mod wasm;
+        pub use crate::wasm::*;
+    } else {
+        pub mod other;
+        pub use crate::other::*;
+    }
+}
diff --git a/crates/livesplit-hotkey/src/other/mod.rs b/crates/livesplit-hotkey/src/other/mod.rs
index 82da62c..afaa38e 100644
--- a/crates/livesplit-hotkey/src/other/mod.rs
+++ b/crates/livesplit-hotkey/src/other/mod.rs
@@ -25,7 +25,7 @@ impl Hook {
     }
 }
 
-use std::{result::Result as StdResult, str::FromStr};
+use core::{result::Result as StdResult, str::FromStr};
 
 impl FromStr for KeyCode {
     type Err = ();
diff --git a/crates/livesplit-title-abbreviations/Cargo.toml b/crates/livesplit-title-abbreviations/Cargo.toml
index cefad33..2b25456 100644
--- a/crates/livesplit-title-abbreviations/Cargo.toml
+++ b/crates/livesplit-title-abbreviations/Cargo.toml
@@ -10,4 +10,4 @@ keywords = ["speedrun", "timer", "livesplit", "title", "abbreviation"]
 edition = "2018"
 
 [dependencies]
-unicase = "2.2.0"
+unicase = "2.6.0"
diff --git a/crates/livesplit-title-abbreviations/src/lib.rs b/crates/livesplit-title-abbreviations/src/lib.rs
index 97cfccf..74ca01d 100644
--- a/crates/livesplit-title-abbreviations/src/lib.rs
+++ b/crates/livesplit-title-abbreviations/src/lib.rs
@@ -1,5 +1,8 @@
-extern crate unicase;
+#![no_std]
 
+extern crate alloc;
+
+use alloc::{format, string::String, vec, vec::Vec};
 use unicase::UniCase;
 
 fn ends_with_roman_numeral(name: &str) -> bool {
diff --git a/src/analysis/pb_chance/mod.rs b/src/analysis/pb_chance/mod.rs
index 109a781..8c8d065 100644
--- a/src/analysis/pb_chance/mod.rs
+++ b/src/analysis/pb_chance/mod.rs
@@ -9,6 +9,7 @@
 //! PB chance is the percentile at which the Balanced PB algorithm finds the PB.
 
 use crate::{comparison, Run, Segment, TimeSpan, Timer, TimingMethod};
+use crate::platform::prelude::*;
 
 #[cfg(test)]
 mod tests;
diff --git a/src/analysis/sum_of_segments/mod.rs b/src/analysis/sum_of_segments/mod.rs
index 261f0ea..528f0a7 100644
--- a/src/analysis/sum_of_segments/mod.rs
+++ b/src/analysis/sum_of_segments/mod.rs
@@ -14,6 +14,7 @@ pub mod worst;
 mod tests;
 
 use crate::{Segment, Time, TimeSpan, TimingMethod};
+use crate::platform::prelude::*;
 
 /// Describes the shortest amount of time it takes to reach a certain segment.
 /// Since there is the possibility that the shortest path is actually skipping
diff --git a/src/comparison/balanced_pb.rs b/src/comparison/balanced_pb.rs
index 8164c52..398f0bd 100644
--- a/src/comparison/balanced_pb.rs
+++ b/src/comparison/balanced_pb.rs
@@ -13,6 +13,7 @@
 
 use super::{goal, ComparisonGenerator};
 use crate::{Attempt, Segment, TimingMethod};
+use crate::platform::prelude::*;
 
 /// The Comparison Generator for calculating a comparison which has the same
 /// final time as the runner's Personal Best. Unlike the Personal Best however,
diff --git a/src/comparison/best_segments.rs b/src/comparison/best_segments.rs
index d4617d1..fa4d85d 100644
--- a/src/comparison/best_segments.rs
+++ b/src/comparison/best_segments.rs
@@ -3,6 +3,7 @@
 use super::ComparisonGenerator;
 use crate::analysis::sum_of_segments::best::calculate;
 use crate::{Attempt, Segment, Time, TimingMethod};
+use crate::platform::prelude::*;
 
 /// The Comparison Generator for calculating the Best Segments of a Run.
 #[derive(Copy, Clone, Debug)]
diff --git a/src/comparison/goal.rs b/src/comparison/goal.rs
index eedc4ce..e6c5533 100644
--- a/src/comparison/goal.rs
+++ b/src/comparison/goal.rs
@@ -7,6 +7,7 @@
 
 use crate::{Segment, Time, TimeSpan, TimingMethod};
 use ordered_float::OrderedFloat;
+use crate::platform::prelude::*;
 
 /// The default name of the goal comparison.
 pub const NAME: &str = "Goal";
diff --git a/src/comparison/median_segments.rs b/src/comparison/median_segments.rs
index f0df843..13c5ffc 100644
--- a/src/comparison/median_segments.rs
+++ b/src/comparison/median_segments.rs
@@ -6,6 +6,7 @@
 use super::ComparisonGenerator;
 use crate::{Attempt, Segment, TimeSpan, TimingMethod};
 use ordered_float::OrderedFloat;
+use crate::platform::prelude::*;
 
 /// The Comparison Generator for calculating the Median Segments of a Run. The
 /// Median Segments are calculated through a weighted median that gives more
diff --git a/src/comparison/mod.rs b/src/comparison/mod.rs
index 5671182..cd3b213 100644
--- a/src/comparison/mod.rs
+++ b/src/comparison/mod.rs
@@ -26,7 +26,8 @@ pub use self::none::None;
 pub use self::worst_segments::WorstSegments;
 
 use crate::{Attempt, Segment, Timer};
-use std::fmt::Debug;
+use core::fmt::Debug;
+use crate::platform::prelude::*;
 
 /// Defines the Personal Best comparison. This module mostly just serves for
 /// providing the names of the comparison, as the Personal Best is not a
diff --git a/src/comparison/worst_segments.rs b/src/comparison/worst_segments.rs
index 22a544a..7f44bda 100644
--- a/src/comparison/worst_segments.rs
+++ b/src/comparison/worst_segments.rs
@@ -3,6 +3,7 @@
 use super::ComparisonGenerator;
 use crate::analysis::sum_of_segments::worst::calculate;
 use crate::{Attempt, Segment, Time, TimingMethod};
+use crate::platform::prelude::*;
 
 /// The Comparison Generator for calculating the Worst Segments of a Run.
 #[derive(Copy, Clone, Debug)]
diff --git a/src/component/blank_space.rs b/src/component/blank_space.rs
index 9703404..b6875a9 100644
--- a/src/component/blank_space.rs
+++ b/src/component/blank_space.rs
@@ -3,11 +3,10 @@
 //! anything other than a background. It mostly serves as padding between other
 //! components.
 
+use crate::platform::prelude::*;
 use crate::settings::{Field, Gradient, SettingsDescription, Value};
 use crate::Timer;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
 
 /// The Blank Space Component is simply an empty component that doesn't show
 /// anything other than a background. It mostly serves as padding between other
@@ -45,13 +44,14 @@ pub struct State {
     pub size: u32,
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/current_comparison.rs b/src/component/current_comparison.rs
index 7f4b861..dac8da2 100644
--- a/src/component/current_comparison.rs
+++ b/src/component/current_comparison.rs
@@ -3,6 +3,7 @@
 //! comparison that is currently selected to be compared against.
 
 use super::key_value;
+use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use crate::Timer;
 use serde::{Deserialize, Serialize};
diff --git a/src/component/current_pace.rs b/src/component/current_pace.rs
index fc28913..9b03976 100644
--- a/src/component/current_pace.rs
+++ b/src/component/current_pace.rs
@@ -5,11 +5,12 @@
 
 use super::key_value;
 use crate::analysis::current_pace;
+use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use crate::timing::formatter::{Accuracy, Regular, TimeFormatter};
 use crate::{comparison, Timer, TimerPhase};
+use alloc::borrow::Cow;
 use serde::{Deserialize, Serialize};
-use std::borrow::Cow;
 
 /// The Current Pace Component is a component that shows a prediction of the
 /// current attempt's final time, if the current attempt's pace matches the
diff --git a/src/component/delta/mod.rs b/src/component/delta/mod.rs
index 32e1a8d..bf4191c 100644
--- a/src/component/delta/mod.rs
+++ b/src/component/delta/mod.rs
@@ -4,11 +4,12 @@
 
 use super::key_value;
 use crate::analysis::{delta, state_helper};
+use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value};
 use crate::timing::formatter::{Accuracy, Delta, TimeFormatter};
 use crate::{comparison, GeneralLayoutSettings, Timer};
+use alloc::borrow::Cow;
 use serde::{Deserialize, Serialize};
-use std::borrow::Cow;
 
 #[cfg(test)]
 mod tests;
diff --git a/src/component/detailed_timer/mod.rs b/src/component/detailed_timer/mod.rs
index 87722ae..8153509 100644
--- a/src/component/detailed_timer/mod.rs
+++ b/src/component/detailed_timer/mod.rs
@@ -15,8 +15,7 @@ use crate::timing::formatter::{
 };
 use crate::{GeneralLayoutSettings, Segment, TimeSpan, Timer, TimerPhase, TimingMethod};
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
+use crate::platform::prelude::*;
 
 #[cfg(test)]
 mod tests;
@@ -110,13 +109,14 @@ impl Default for Settings {
     }
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/graph.rs b/src/component/graph.rs
index ff23402..c303e52 100644
--- a/src/component/graph.rs
+++ b/src/component/graph.rs
@@ -5,10 +5,9 @@
 
 use crate::settings::{Color, Field, SettingsDescription, Value};
 use crate::{analysis, comparison, GeneralLayoutSettings, TimeSpan, Timer, TimerPhase};
+use alloc::borrow::Cow;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::borrow::Cow;
-use std::io::Write;
+use crate::platform::prelude::*;
 
 const GRAPH_EDGE_VALUE: f32 = 200.0;
 const GRAPH_EDGE_MIN: f32 = 5.0;
@@ -137,13 +136,14 @@ impl Default for Settings {
     }
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/key_value.rs b/src/component/key_value.rs
index b6261fa..100b913 100644
--- a/src/component/key_value.rs
+++ b/src/component/key_value.rs
@@ -3,13 +3,12 @@
 //! components. They all share the same visual appearance and thus use the same
 //! state object representation.
 
+use crate::platform::prelude::*;
 use crate::settings::{Color, Gradient, SemanticColor};
+use core::marker::PhantomData;
 use palette::rgb::Rgb;
 use palette::Alpha;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
-use std::marker::PhantomData;
 
 /// The state object describes the information to visualize for a key value
 /// based component.
@@ -36,13 +35,14 @@ pub struct State {
     pub display_two_rows: bool,
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/pb_chance.rs b/src/component/pb_chance.rs
index ab8dd8d..d3ff26a 100644
--- a/src/component/pb_chance.rs
+++ b/src/component/pb_chance.rs
@@ -8,6 +8,7 @@ use super::key_value;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use crate::{analysis::pb_chance, Timer};
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// The PB Chance Component is a component that shows how likely it is to beat
 /// the Personal Best. If there is no active attempt it shows the general chance
diff --git a/src/component/possible_time_save.rs b/src/component/possible_time_save.rs
index 85b12f3..7de1810 100644
--- a/src/component/possible_time_save.rs
+++ b/src/component/possible_time_save.rs
@@ -10,8 +10,9 @@ use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use crate::timing::formatter::{Accuracy, PossibleTimeSave, TimeFormatter};
 use crate::{comparison, Timer, TimerPhase};
 use serde::{Deserialize, Serialize};
-use std::borrow::Cow;
-use std::fmt::Write as FmtWrite;
+use alloc::borrow::Cow;
+use core::fmt::Write as FmtWrite;
+use crate::platform::prelude::*;
 
 /// The Possible Time Save Component is a component that shows how much time the
 /// chosen comparison could've saved for the current segment, based on the Best
diff --git a/src/component/previous_segment.rs b/src/component/previous_segment.rs
index 85c7baf..e268d3f 100644
--- a/src/component/previous_segment.rs
+++ b/src/component/previous_segment.rs
@@ -10,8 +10,9 @@ use crate::settings::{Color, Field, Gradient, SemanticColor, SettingsDescription
 use crate::timing::formatter::{Accuracy, Delta, PossibleTimeSave, TimeFormatter};
 use crate::{analysis, comparison, GeneralLayoutSettings, Timer, TimerPhase};
 use serde::{Deserialize, Serialize};
-use std::borrow::Cow;
-use std::fmt::Write;
+use alloc::borrow::Cow;
+use core::fmt::Write as FmtWrite;
+use crate::platform::prelude::*;
 
 /// The Previous Segment Component is a component that shows how much time was
 /// saved or lost during the previous segment based on the chosen comparison.
diff --git a/src/component/separator.rs b/src/component/separator.rs
index 172431c..9781d91 100644
--- a/src/component/separator.rs
+++ b/src/component/separator.rs
@@ -5,8 +5,6 @@
 use crate::settings::{SettingsDescription, Value};
 use crate::Timer;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
 
 /// The Separator Component is a simple component that only serves to render
 /// separators between components.
@@ -17,13 +15,14 @@ pub struct Component;
 #[derive(Serialize, Deserialize)]
 pub struct State;
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/splits/column.rs b/src/component/splits/column.rs
index de5a1c6..4d40a78 100644
--- a/src/component/splits/column.rs
+++ b/src/component/splits/column.rs
@@ -6,6 +6,7 @@ use crate::{
     GeneralLayoutSettings, Segment, TimeSpan, Timer, TimingMethod,
 };
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// The settings of an individual column showing timing information on each
 /// split.
diff --git a/src/component/splits/mod.rs b/src/component/splits/mod.rs
index 7679bd5..6c4372e 100644
--- a/src/component/splits/mod.rs
+++ b/src/component/splits/mod.rs
@@ -5,16 +5,15 @@
 //! list provides scrolling functionality, so not every segment needs to be
 //! shown all the time.
 
+use crate::platform::prelude::*;
 use crate::{
     settings::{
         CachedImageId, Color, Field, Gradient, ImageData, ListGradient, SettingsDescription, Value,
     },
     GeneralLayoutSettings, Timer,
 };
+use core::cmp::{max, min};
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::cmp::{max, min};
-use std::io::Write;
 
 #[cfg(test)]
 mod tests;
@@ -200,13 +199,14 @@ impl Default for Settings {
     }
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/sum_of_best.rs b/src/component/sum_of_best.rs
index 2ad5ef7..fecaec2 100644
--- a/src/component/sum_of_best.rs
+++ b/src/component/sum_of_best.rs
@@ -9,6 +9,7 @@
 
 use super::key_value;
 use crate::analysis::sum_of_segments::calculate_best;
+use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use crate::timing::formatter::{Accuracy, Regular, TimeFormatter};
 use crate::Timer;
diff --git a/src/component/text.rs b/src/component/text.rs
index eaf20f7..d0e8a18 100644
--- a/src/component/text.rs
+++ b/src/component/text.rs
@@ -5,11 +5,10 @@
 
 use super::key_value;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
+use alloc::borrow::Cow;
+use core::mem::replace;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::borrow::Cow;
-use std::io::Write;
-use std::mem::replace;
+use crate::platform::prelude::*;
 
 /// The Text Component simply visualizes any given text. This can either be a
 /// single centered text, or split up into a left and right text, which is
@@ -125,13 +124,14 @@ impl Default for Settings {
     }
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/timer.rs b/src/component/timer.rs
index 431071b..21320b4 100644
--- a/src/component/timer.rs
+++ b/src/component/timer.rs
@@ -4,14 +4,12 @@
 //! current attempt is doing compared to the chosen comparison.
 
 use crate::analysis::split_color;
+use crate::palette::{rgb::LinSrgb, Hsv};
+use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SemanticColor, SettingsDescription, Value};
 use crate::timing::formatter::{timer as formatter, Accuracy, DigitsFormat, TimeFormatter};
 use crate::{GeneralLayoutSettings, TimeSpan, Timer, TimerPhase, TimingMethod};
-use palette::rgb::LinSrgb;
-use palette::Hsv;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
 
 /// The Timer Component is a component that shows the total time of the current
 /// attempt as a digital clock. The color of the time shown is based on a how
@@ -81,13 +79,14 @@ pub struct State {
     pub height: u32,
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/title/mod.rs b/src/component/title/mod.rs
index d850d31..f968015 100644
--- a/src/component/title/mod.rs
+++ b/src/component/title/mod.rs
@@ -9,8 +9,7 @@ use crate::{
     Timer, TimerPhase,
 };
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
+use crate::platform::prelude::*;
 
 #[cfg(test)]
 mod tests;
@@ -119,13 +118,14 @@ impl Default for Settings {
     }
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/component/total_playtime.rs b/src/component/total_playtime.rs
index ac6db1b..f829645 100644
--- a/src/component/total_playtime.rs
+++ b/src/component/total_playtime.rs
@@ -4,6 +4,7 @@
 
 use super::key_value;
 use crate::analysis::total_playtime;
+use crate::platform::prelude::*;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use crate::timing::formatter::{Days, Regular, TimeFormatter};
 use crate::Timer;
diff --git a/src/hotkey_config.rs b/src/hotkey_config.rs
index 4c8eff2..e65519c 100644
--- a/src/hotkey_config.rs
+++ b/src/hotkey_config.rs
@@ -1,10 +1,9 @@
 #![allow(clippy::trivially_copy_pass_by_ref)]
 
 use crate::hotkey::KeyCode;
+use crate::platform::prelude::*;
 use crate::settings::{Field, SettingsDescription, Value};
 use serde::{Deserialize, Serialize};
-use serde_json::{self, from_reader, to_writer};
-use std::io::{Read, Write};
 
 /// The configuration to use for a Hotkey System. It describes with keys to use
 /// as hotkeys for the different actions.
@@ -170,18 +169,20 @@ impl HotkeyConfig {
     }
 
     /// Decodes the hotkey configuration from JSON.
+    #[cfg(feature = "std")]
     pub fn from_json<R>(reader: R) -> serde_json::Result<Self>
     where
-        R: Read,
+        R: std::io::Read,
     {
-        from_reader(reader)
+        serde_json::from_reader(reader)
     }
 
     /// Encodes the hotkey configuration as JSON.
+    #[cfg(feature = "std")]
     pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
diff --git a/src/hotkey_system.rs b/src/hotkey_system.rs
index 02e360f..52a29b7 100644
--- a/src/hotkey_system.rs
+++ b/src/hotkey_system.rs
@@ -1,7 +1,7 @@
 use crate::hotkey::{Hook, KeyCode};
 use crate::{HotkeyConfig, SharedTimer};
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::Arc;
+use alloc::sync::Arc;
+use core::sync::atomic::{AtomicBool, Ordering};
 
 pub use crate::hotkey::{Error, Result};
 
diff --git a/src/layout/component.rs b/src/layout/component.rs
index 7af44dc..c184790 100644
--- a/src/layout/component.rs
+++ b/src/layout/component.rs
@@ -6,7 +6,8 @@ use crate::component::{
 };
 use crate::settings::{SettingsDescription, Value};
 use crate::Timer;
-use std::borrow::Cow;
+use alloc::borrow::Cow;
+use crate::platform::prelude::*;
 
 /// A Component provides information about a run in a way that is easy to
 /// visualize. This type can store any of the components provided by this crate.
diff --git a/src/layout/component_settings.rs b/src/layout/component_settings.rs
index 420d381..e4b1a8a 100644
--- a/src/layout/component_settings.rs
+++ b/src/layout/component_settings.rs
@@ -5,6 +5,7 @@ use crate::component::{
     total_playtime,
 };
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// The settings for one of the components available.
 #[derive(Clone, Serialize, Deserialize)]
diff --git a/src/layout/component_state.rs b/src/layout/component_state.rs
index bae9892..43d535a 100644
--- a/src/layout/component_state.rs
+++ b/src/layout/component_state.rs
@@ -2,6 +2,7 @@ use crate::component::{
     blank_space, detailed_timer, graph, key_value, separator, splits, text, timer, title,
 };
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// The state object for one of the components available.
 #[derive(Serialize, Deserialize)]
diff --git a/src/layout/editor/mod.rs b/src/layout/editor/mod.rs
index 9df387a..ca15fdb 100644
--- a/src/layout/editor/mod.rs
+++ b/src/layout/editor/mod.rs
@@ -7,7 +7,7 @@
 use super::{Component, Layout, LayoutState};
 use crate::settings::Value;
 use crate::Timer;
-use std::result::Result as StdResult;
+use core::result::Result as StdResult;
 
 mod state;
 
diff --git a/src/layout/editor/state.rs b/src/layout/editor/state.rs
index 9000171..f68dbf3 100644
--- a/src/layout/editor/state.rs
+++ b/src/layout/editor/state.rs
@@ -1,8 +1,7 @@
 use super::Editor;
 use crate::settings::SettingsDescription;
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result as JsonResult};
-use std::io::Write;
+use crate::platform::prelude::*;
 
 /// Represents the current state of the Layout Editor in order to visualize it
 /// properly.
@@ -38,13 +37,14 @@ pub struct Buttons {
     pub can_move_down: bool,
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> JsonResult<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/layout/general_settings.rs b/src/layout/general_settings.rs
index 93e6c22..8cc0cac 100644
--- a/src/layout/general_settings.rs
+++ b/src/layout/general_settings.rs
@@ -1,6 +1,7 @@
 use super::LayoutDirection;
 use crate::settings::{Color, Field, Gradient, SettingsDescription, Value};
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// The general settings of the layout that apply to all components.
 #[derive(Clone, Serialize, Deserialize)]
diff --git a/src/layout/layout_settings.rs b/src/layout/layout_settings.rs
index d71016d..174e444 100644
--- a/src/layout/layout_settings.rs
+++ b/src/layout/layout_settings.rs
@@ -1,7 +1,6 @@
 use super::{ComponentSettings, GeneralSettings};
 use serde::{Deserialize, Serialize};
-use serde_json::{from_reader, to_writer, Result};
-use std::io::{Read, Write};
+use crate::platform::prelude::*;
 
 /// Describes a whole layout by its settings in a way that can easily be
 /// serialized and deserialized.
@@ -13,20 +12,21 @@ pub struct LayoutSettings {
     pub general: GeneralSettings,
 }
 
+#[cfg(feature = "std")]
 impl LayoutSettings {
     /// Decodes the layout's settings from JSON.
-    pub fn from_json<R>(reader: R) -> Result<LayoutSettings>
+    pub fn from_json<R>(reader: R) -> serde_json::Result<LayoutSettings>
     where
-        R: Read,
+        R: std::io::Read,
     {
-        from_reader(reader)
+        serde_json::from_reader(reader)
     }
 
     /// Encodes the layout's settings as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
diff --git a/src/layout/layout_state.rs b/src/layout/layout_state.rs
index 564effb..9874a44 100644
--- a/src/layout/layout_state.rs
+++ b/src/layout/layout_state.rs
@@ -1,8 +1,7 @@
 use super::{ComponentState, LayoutDirection};
 use crate::settings::{Color, Gradient};
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result};
-use std::io::Write;
+use crate::platform::prelude::*;
 
 /// The state object describes the information to visualize for the layout.
 #[derive(Serialize, Deserialize)]
@@ -21,12 +20,13 @@ pub struct LayoutState {
     pub text_color: Color,
 }
 
+#[cfg(feature = "std")]
 impl LayoutState {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> Result<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, &self)
+        serde_json::to_writer(writer, self)
     }
 }
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index c0613ec..a1da843 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -10,6 +10,7 @@ mod general_settings;
 mod layout_direction;
 mod layout_settings;
 mod layout_state;
+#[cfg(feature = "std")]
 pub mod parser;
 
 pub use self::component::Component;
@@ -23,6 +24,7 @@ pub use self::layout_state::LayoutState;
 
 use crate::component::{previous_segment, splits, timer, title};
 use crate::timing::Timer;
+use crate::platform::prelude::*;
 
 /// A Layout allows you to combine multiple components together to visualize a
 /// variety of information the runner is interested in.
diff --git a/src/layout/parser.rs b/src/layout/parser.rs
index 237f310..b6b6063 100644
--- a/src/layout/parser.rs
+++ b/src/layout/parser.rs
@@ -43,17 +43,17 @@ pub enum Error {
     /// Failed to decode a string slice as UTF-8.
     Utf8Str {
         /// The underlying error.
-        source: std::str::Utf8Error,
+        source: core::str::Utf8Error,
     },
     /// Failed to decode a string as UTF-8.
     Utf8String {
         /// The underlying error.
-        source: std::string::FromUtf8Error,
+        source: alloc::string::FromUtf8Error,
     },
     /// Failed to parse an integer.
     ParseInt {
         /// The underlying error.
-        source: std::num::ParseIntError,
+        source: core::num::ParseIntError,
     },
     /// Failed to parse a boolean.
     ParseBool,
@@ -76,7 +76,7 @@ pub enum Error {
 }
 
 /// The Result type for parsing layout files of the original LiveSplit.
-pub type Result<T> = std::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, Error>;
 
 enum GradientKind {
     Transparent,
diff --git a/src/lib.rs b/src/lib.rs
index 2019cd8..6b851f3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,6 +12,7 @@
     clippy::redundant_closure_call,
     clippy::new_ret_no_self
 )]
+#![cfg_attr(not(feature = "std"), no_std)]
 
 //! livesplit-core is a library that provides a lot of functionality for creating a speedrun timer.
 //!
@@ -46,6 +47,8 @@
 //! assert_eq!(timer.current_phase(), TimerPhase::NotRunning);
 //! ```
 
+extern crate alloc;
+
 mod platform;
 
 #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
@@ -60,7 +63,9 @@ macro_rules! catch {
 pub mod analysis;
 pub mod comparison;
 pub mod component;
+#[cfg(feature = "std")]
 mod hotkey_config;
+#[cfg(feature = "std")]
 mod hotkey_system;
 pub mod layout;
 #[cfg(feature = "rendering")]
@@ -70,21 +75,26 @@ pub mod settings;
 #[cfg(test)]
 pub mod tests_helper;
 pub mod timing;
+#[cfg(feature = "std")]
 mod xml_util;
 
-pub use {
-    crate::{
-        hotkey_config::HotkeyConfig,
-        hotkey_system::HotkeySystem,
-        layout::{
-            Component, Editor as LayoutEditor, GeneralSettings as GeneralLayoutSettings, Layout,
-        },
-        run::{Attempt, Editor as RunEditor, Run, RunMetadata, Segment, SegmentHistory},
-        timing::{
-            AtomicDateTime, GameTime, RealTime, SharedTimer, Time, TimeSpan, TimeStamp, Timer,
-            TimerPhase, TimingMethod,
-        },
+pub use crate::{
+    layout::{Component, Editor as LayoutEditor, GeneralSettings as GeneralLayoutSettings, Layout},
+    platform::{indexmap, DateTime, Utc},
+    run::{Attempt, Editor as RunEditor, Run, RunMetadata, Segment, SegmentHistory},
+    timing::{
+        AtomicDateTime, GameTime, RealTime, Time, TimeSpan, TimeStamp, Timer, TimerPhase,
+        TimingMethod,
     },
-    chrono::{DateTime, Utc},
-    indexmap, livesplit_hotkey as hotkey, palette, parking_lot,
 };
+pub use livesplit_hotkey as hotkey;
+pub use palette;
+
+#[cfg(not(feature = "std"))]
+pub use crate::platform::{register_clock, Clock, Duration};
+
+#[cfg(feature = "std")]
+pub use parking_lot;
+
+#[cfg(feature = "std")]
+pub use crate::{hotkey_config::HotkeyConfig, hotkey_system::HotkeySystem, timing::SharedTimer};
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 0bb18be..056c198 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -1,9 +1,21 @@
-#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
-mod wasm;
-#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
-pub use self::wasm::*;
+cfg_if::cfg_if! {
+    if #[cfg(not(feature = "std"))] {
+        mod no_std;
+        pub use self::no_std::*;
+    } else if #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] {
+        mod wasm;
+        pub use self::wasm::*;
+    } else {
+        mod normal;
+        pub use self::normal::*;
+    }
+}
 
-#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
-mod normal;
-#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
-pub use self::normal::*;
+pub(crate) mod prelude {
+    pub use alloc::borrow::ToOwned;
+    pub use alloc::boxed::Box;
+    pub use alloc::string::String;
+    pub use alloc::string::ToString;
+    pub use alloc::vec::Vec;
+    pub use alloc::{format, vec};
+}
diff --git a/src/platform/no_std/indexmap.rs b/src/platform/no_std/indexmap.rs
new file mode 100644
index 0000000..3783634
--- /dev/null
+++ b/src/platform/no_std/indexmap.rs
@@ -0,0 +1,127 @@
+//! no-std polyfill for indexmap.
+
+/// IndexMap is a hash table where the iteration order of the key-value pairs is
+/// independent of the hash values of the keys.
+pub mod map {
+    use crate::platform::prelude::*;
+    use core::{fmt, marker::PhantomData};
+    use serde::{
+        de::{MapAccess, Visitor},
+        ser::SerializeMap,
+        Deserialize, Deserializer, Serialize, Serializer,
+    };
+
+    /// A hash table where the iteration order of the key-value pairs is
+    /// independent of the hash values of the keys.
+    #[derive(Default, Clone, Debug, PartialEq)]
+    pub struct IndexMap<K, V>(Vec<(K, V)>);
+
+    /// An iterator over the entries of a IndexMap.
+    pub struct Iter<'a, K, V>(core::slice::Iter<'a, (K, V)>);
+
+    impl<K: PartialEq, V> IndexMap<K, V> {
+        /// Insert a key-value pair in the map.
+        pub fn insert(&mut self, key: K, value: V) {
+            if let Some(index) = self.0.iter().position(|(k, _)| k == &key) {
+                self.0[index] = (key, value);
+            } else {
+                self.0.push((key, value));
+            }
+        }
+
+        /// Remove the key-value pair equivalent to key.
+        pub fn shift_remove<K2>(&mut self, key: &K2)
+        where
+            K: core::borrow::Borrow<K2>,
+            K2: ?Sized + PartialEq,
+        {
+            if let Some(index) = self.0.iter().position(|(k, _)| k.borrow() == key) {
+                self.0.remove(index);
+            }
+        }
+
+        /// Return an iterator over the key-value pairs of the map, in their order.
+        pub fn iter(&self) -> Iter<'_, K, V> {
+            Iter(self.0.iter())
+        }
+
+        /// Remove all key-value pairs in the map, while preserving its capacity.
+        pub fn clear(&mut self) {
+            self.0.clear();
+        }
+    }
+
+    impl<'a, K, V> Iterator for Iter<'a, K, V> {
+        type Item = (&'a K, &'a V);
+        fn next(&mut self) -> Option<Self::Item> {
+            self.0.next().map(|(a, b)| (a, b))
+        }
+    }
+
+    impl<K, V> Serialize for IndexMap<K, V>
+    where
+        K: PartialEq + Serialize,
+        V: Serialize,
+    {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: Serializer,
+        {
+            let mut map = serializer.serialize_map(Some(self.0.len()))?;
+            for (k, v) in self.iter() {
+                map.serialize_entry(k, v)?;
+            }
+            map.end()
+        }
+    }
+
+    impl<'de, K, V> Deserialize<'de> for IndexMap<K, V>
+    where
+        K: PartialEq + Deserialize<'de>,
+        V: Deserialize<'de>,
+    {
+        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: Deserializer<'de>,
+        {
+            deserializer.deserialize_map(IndexMapVisitor::new())
+        }
+    }
+
+    struct IndexMapVisitor<K, V> {
+        marker: PhantomData<fn() -> IndexMap<K, V>>,
+    }
+
+    impl<K, V> IndexMapVisitor<K, V> {
+        fn new() -> Self {
+            IndexMapVisitor {
+                marker: PhantomData,
+            }
+        }
+    }
+
+    impl<'de, K, V> Visitor<'de> for IndexMapVisitor<K, V>
+    where
+        K: PartialEq + Deserialize<'de>,
+        V: Deserialize<'de>,
+    {
+        type Value = IndexMap<K, V>;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+            formatter.write_str("a map")
+        }
+
+        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
+        where
+            M: MapAccess<'de>,
+        {
+            let mut map = IndexMap(Vec::with_capacity(access.size_hint().unwrap_or(0)));
+
+            while let Some((key, value)) = access.next_entry()? {
+                map.insert(key, value);
+            }
+
+            Ok(map)
+        }
+    }
+}
diff --git a/src/platform/no_std/mod.rs b/src/platform/no_std/mod.rs
new file mode 100644
index 0000000..efa0ffb
--- /dev/null
+++ b/src/platform/no_std/mod.rs
@@ -0,0 +1,3 @@
+mod time;
+pub use self::time::*;
+pub mod indexmap;
diff --git a/src/platform/no_std/time.rs b/src/platform/no_std/time.rs
new file mode 100644
index 0000000..5273a2e
--- /dev/null
+++ b/src/platform/no_std/time.rs
@@ -0,0 +1,159 @@
+use crate::platform::prelude::*;
+use core::marker::PhantomData;
+use core::ops::{Add, Sub};
+use core::sync::atomic::{self, AtomicPtr};
+use derive_more::{Add, Neg, Sub};
+
+/// A clock is a global handler that can be registered for providing the high
+/// precision time stamps on a `no_std` target.
+pub trait Clock: 'static {
+    /// Returns the current point in time as a Duration.
+    fn now(&self) -> Duration;
+}
+
+static CLOCK: AtomicPtr<Box<dyn Clock>> = AtomicPtr::new(core::ptr::null_mut());
+
+/// Registers a clock as the global handler for providing the high precision
+/// time stamps on a `no_std` target.
+pub fn register_clock(clock: impl Clock) {
+    let clock: Box<dyn Clock> = Box::new(clock);
+    let clock = Box::new(clock);
+    // FIXME: This isn't entirely clean as this should really be
+    // compare_and_swap, but we can't do that on every platform.
+    if !CLOCK.load(atomic::Ordering::SeqCst).is_null() {
+        panic!("The clock has already been registered");
+    }
+    CLOCK.store(Box::into_raw(clock), atomic::Ordering::SeqCst);
+}
+
+#[derive(Copy, Clone, PartialEq, Debug, Default)]
+struct FFIDateTime {
+    year: u16,
+    month: u8,
+    day: u8,
+    hours: u8,
+    minutes: u8,
+    seconds: u8,
+}
+
+/// The local time zone.
+pub struct Local;
+
+/// A date and a time of day.
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub struct DateTime<T>(PhantomData<T>, FFIDateTime);
+
+/// A time zone.
+pub trait TimeZone: Sized {
+    fn datetime_from_str(&self, s: &str, fmt: &str) -> Result<DateTime<Self>, ParseError>;
+}
+
+/// The UTC time zone.
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub struct Utc;
+
+/// Failed to parse a time.
+#[derive(Debug)]
+pub struct ParseError;
+
+/// A span of time.
+#[derive(Add, Sub, Neg, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub struct Duration(i128);
+
+/// A point in time.
+#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Debug)]
+pub struct Instant(i128);
+
+impl Instant {
+    /// Accesses the current point in time.
+    pub fn now() -> Self {
+        let clock = CLOCK.load(atomic::Ordering::SeqCst);
+        if clock.is_null() {
+            panic!("No clock registered");
+        }
+        let clock = unsafe { &*clock };
+        let Duration(t) = clock.now();
+        Instant(t)
+    }
+}
+
+impl Sub for Instant {
+    type Output = Duration;
+
+    fn sub(self, rhs: Instant) -> Duration {
+        Duration(self.0 - rhs.0)
+    }
+}
+
+impl Duration {
+    /// Creates a duration from the provided amount of nanoseconds.
+    pub fn nanoseconds(nanos: i64) -> Self {
+        Duration(nanos as _)
+    }
+
+    /// Creates a duration from the provided amount of microseconds.
+    pub fn microseconds(micros: i64) -> Self {
+        Duration(micros as i128 * 1_000)
+    }
+
+    /// Returns the total amount of microseconds.
+    pub fn num_microseconds(self) -> Option<i128> {
+        Some(self.0 / 1_000)
+    }
+
+    /// Converts a core::time::Duration to a Duration.
+    pub fn from_std(val: core::time::Duration) -> Option<Self> {
+        let secs = val.as_secs() as i128;
+        let secs_as_nanos = secs * 1_000_000_000;
+        Some(Duration(val.subsec_nanos() as i128 + secs_as_nanos))
+    }
+}
+
+impl TimeZone for Utc {
+    fn datetime_from_str(&self, _: &str, _: &str) -> Result<DateTime<Self>, ParseError> {
+        Ok(DateTime(PhantomData, Default::default()))
+    }
+}
+
+impl<T> Add<Duration> for DateTime<T> {
+    type Output = DateTime<T>;
+
+    fn add(self, _: Duration) -> DateTime<T> {
+        self
+    }
+}
+
+impl<T> DateTime<T> {
+    /// Returns the duration between two date times.
+    pub fn signed_duration_since<Tz2: TimeZone>(self, _: DateTime<Tz2>) -> Duration {
+        Duration::nanoseconds(0)
+    }
+
+    /// Formats the date time as a string.
+    pub fn format(&self, _: &str) -> String {
+        format!(
+            "{:02}/{:02}/{:04} {:02}:{:02}:{:02}",
+            self.1.month, self.1.day, self.1.year, self.1.hours, self.1.minutes, self.1.seconds
+        )
+    }
+
+    /// Formats the date time as a RFC 2822 string.
+    pub fn to_rfc2822(&self) -> &'static str {
+        "Tue, 1 Jul 2003 10:52:37 +0200"
+    }
+
+    /// Formats the date time as a RFC 3339 string.
+    pub fn to_rfc3339(&self) -> &'static str {
+        "1996-12-19T16:39:57-08:00"
+    }
+
+    /// Changes the time zone of a date time (and adjust the time accordingly).
+    pub fn with_timezone<Tz2>(&self, _: &Tz2) -> DateTime<Tz2> {
+        DateTime(PhantomData, Default::default())
+    }
+}
+
+/// Returns the current date time.
+pub fn utc_now() -> DateTime<Utc> {
+    DateTime(PhantomData, Default::default())
+}
diff --git a/src/platform/normal/mod.rs b/src/platform/normal/mod.rs
index 2631825..e95a0b5 100644
--- a/src/platform/normal/mod.rs
+++ b/src/platform/normal/mod.rs
@@ -1,8 +1,8 @@
 #![allow(missing_docs)]
 
-pub use std::time::{Duration, Instant};
-
-use chrono::{DateTime, Utc};
+pub use chrono::{DateTime, Duration, Local, Utc};
+pub use indexmap;
+pub use std::time::Instant;
 
 pub fn utc_now() -> DateTime<Utc> {
     Utc::now()
diff --git a/src/platform/wasm/mod.rs b/src/platform/wasm/mod.rs
index cbb91df..b028711 100644
--- a/src/platform/wasm/mod.rs
+++ b/src/platform/wasm/mod.rs
@@ -3,8 +3,10 @@
 mod time;
 
 pub use self::time::*;
+pub use chrono::{DateTime, Duration, Local, Utc};
+pub use indexmap;
 
-use std::mem;
+use core::mem;
 
 #[no_mangle]
 pub extern "C" fn alloc(size: usize) -> *mut u8 {
@@ -21,7 +23,7 @@ pub extern "C" fn dealloc(ptr: *mut u8, cap: usize) {
     }
 }
 
-use chrono::{DateTime, NaiveDateTime, Utc};
+use chrono::NaiveDateTime;
 use std::mem::MaybeUninit;
 
 #[repr(C)]
diff --git a/src/platform/wasm/time.rs b/src/platform/wasm/time.rs
index 60b3b8a..3e80837 100644
--- a/src/platform/wasm/time.rs
+++ b/src/platform/wasm/time.rs
@@ -1,5 +1,5 @@
+use core::{ops::Sub, time::Duration};
 use ordered_float::OrderedFloat;
-use std::ops::Sub;
 
 extern "C" {
     fn Instant_now() -> f64;
@@ -24,5 +24,3 @@ impl Sub for Instant {
         Duration::new(secs, nanos)
     }
 }
-
-pub use std::time::Duration;
diff --git a/src/rendering/software/mod.rs b/src/rendering/software/mod.rs
index 8141b7b..cbecc50 100644
--- a/src/rendering/software/mod.rs
+++ b/src/rendering/software/mod.rs
@@ -7,7 +7,7 @@
 
     // Without MMX the floating point calculations don't follow IEEE 754. So the
     // tests fail to produce the correct checksums.
-    // TODO: We use SSE as an approximation for the cfg, because MMX isn't
+    // FIXME: We use SSE as an approximation for the cfg, because MMX isn't
     // supported by Rust just yet.
     not(all(
         target_arch = "x86",
diff --git a/src/run/editor/cleaning.rs b/src/run/editor/cleaning.rs
index be4ba65..cb0cc07 100644
--- a/src/run/editor/cleaning.rs
+++ b/src/run/editor/cleaning.rs
@@ -9,9 +9,10 @@
 use crate::analysis::sum_of_segments::{best, track_branch, Prediction};
 use crate::timing::formatter::{Short, TimeFormatter};
 use crate::{Attempt, Run, Segment, TimeSpan, TimingMethod};
-use chrono::Local;
-use std::fmt;
-use std::mem::replace;
+use crate::platform::Local;
+use core::fmt;
+use core::mem::replace;
+use crate::platform::prelude::*;
 
 /// A Sum of Best Cleaner allows you to interactively remove potential issues in
 /// the Segment History that lead to an inaccurate Sum of Best. If you skip a
diff --git a/src/run/editor/fuzzy_list.rs b/src/run/editor/fuzzy_list.rs
index 20c1032..31aebf5 100644
--- a/src/run/editor/fuzzy_list.rs
+++ b/src/run/editor/fuzzy_list.rs
@@ -1,5 +1,6 @@
-use std::collections::BinaryHeap;
-use std::usize;
+use alloc::collections::BinaryHeap;
+use core::usize;
+use crate::platform::prelude::*;
 
 /// With a Fuzzy List, you can implement a fuzzy searching algorithm. The list
 /// stores all the items that can be searched for. With the `search` method you
diff --git a/src/run/editor/mod.rs b/src/run/editor/mod.rs
index 8cd3df3..c84b31f 100644
--- a/src/run/editor/mod.rs
+++ b/src/run/editor/mod.rs
@@ -5,16 +5,17 @@
 //! kind of User Interface.
 
 use super::{ComparisonError, ComparisonResult};
+use crate::platform::prelude::*;
 use crate::timing::ParseError as ParseTimeSpanError;
 use crate::{
     comparison,
     settings::{CachedImageId, Image},
     Run, Segment, Time, TimeSpan, TimingMethod,
 };
+use core::mem::swap;
+use core::num::ParseIntError;
 use odds::slice::rotate_left;
 use snafu::{OptionExt, ResultExt};
-use std::mem::swap;
-use std::num::ParseIntError;
 
 pub mod cleaning;
 mod fuzzy_list;
@@ -29,7 +30,7 @@ pub use self::segment_row::SegmentRow;
 pub use self::state::{Buttons as ButtonsState, Segment as SegmentState, State};
 
 /// Describes an Error that occurred while parsing a time.
-#[derive(Debug, snafu::Snafu, derive_more::From)]
+#[derive(Debug, snafu::Snafu)]
 pub enum ParseError {
     /// Couldn't parse the time.
     ParseTime {
@@ -233,7 +234,7 @@ impl Editor {
     where
         S: AsRef<str>,
     {
-        self.set_offset(offset.as_ref().parse()?);
+        self.set_offset(offset.as_ref().parse().context(ParseTime)?);
         Ok(())
     }
 
@@ -880,7 +881,7 @@ fn parse_positive<S>(time: S) -> Result<Option<TimeSpan>, ParseError>
 where
     S: AsRef<str>,
 {
-    let time = TimeSpan::parse_opt(time)?;
+    let time = TimeSpan::parse_opt(time).context(ParseTime)?;
     if time.map_or(false, |t| t < TimeSpan::zero()) {
         Err(ParseError::NegativeTimeNotAllowed)
     } else {
diff --git a/src/run/editor/state.rs b/src/run/editor/state.rs
index 1256220..fb36ddd 100644
--- a/src/run/editor/state.rs
+++ b/src/run/editor/state.rs
@@ -5,8 +5,7 @@ use crate::settings::{CachedImageId, ImageData};
 use crate::timing::formatter::none_wrapper::EmptyWrapper;
 use crate::timing::formatter::{Accuracy, Short, TimeFormatter};
 use serde::{Deserialize, Serialize};
-use serde_json::{to_writer, Result as JsonResult};
-use std::io::Write;
+use crate::platform::prelude::*;
 
 /// Represents the current state of the Run Editor in order to visualize it
 /// properly.
@@ -93,13 +92,14 @@ pub enum SelectionState {
     Active,
 }
 
+#[cfg(feature = "std")]
 impl State {
     /// Encodes the state object's information as JSON.
-    pub fn write_json<W>(&self, writer: W) -> JsonResult<()>
+    pub fn write_json<W>(&self, writer: W) -> serde_json::Result<()>
     where
-        W: Write,
+        W: std::io::Write,
     {
-        to_writer(writer, self)
+        serde_json::to_writer(writer, self)
     }
 }
 
diff --git a/src/run/mod.rs b/src/run/mod.rs
index c9682d6..ca28760 100644
--- a/src/run/mod.rs
+++ b/src/run/mod.rs
@@ -17,8 +17,10 @@
 
 mod attempt;
 pub mod editor;
+#[cfg(feature = "std")]
 pub mod parser;
 mod run_metadata;
+#[cfg(feature = "std")]
 pub mod saver;
 mod segment;
 mod segment_history;
@@ -34,12 +36,15 @@ pub use segment_history::SegmentHistory;
 
 use crate::comparison::{default_generators, personal_best, ComparisonGenerator};
 use crate::{settings::Image, AtomicDateTime, Time, TimeSpan, TimingMethod};
+use alloc::borrow::Cow;
+use core::cmp::max;
 use ordered_float::OrderedFloat;
-use std::borrow::Cow;
-use std::cmp::max;
-use std::collections::HashSet;
+use hashbrown::HashSet;
+#[cfg(feature = "std")]
 use std::path::PathBuf;
-use unicase;
+#[cfg(not(feature = "std"))]
+use alloc::string::String as PathBuf;
+use crate::platform::prelude::*;
 
 /// A Run stores the split times for a specific game and category of a runner.
 ///
diff --git a/src/run/parser/composite.rs b/src/run/parser/composite.rs
index ecdc2fb..e9a97ea 100644
--- a/src/run/parser/composite.rs
+++ b/src/run/parser/composite.rs
@@ -36,10 +36,10 @@ use super::{
     worstrun, wsplit, TimerKind,
 };
 use crate::Run;
+use core::result::Result as StdResult;
 use snafu::ResultExt;
 use std::io::{self, BufRead, Seek, SeekFrom};
 use std::path::PathBuf;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the Composite
 /// Parser.
diff --git a/src/run/parser/face_split.rs b/src/run/parser/face_split.rs
index 70f99fb..0d916c4 100644
--- a/src/run/parser/face_split.rs
+++ b/src/run/parser/face_split.rs
@@ -1,11 +1,11 @@
 //! Provides the parser for FaceSplit splits files.
 
 use crate::{settings::Image, timing, RealTime, Run, Segment, Time, TimeSpan};
+use alloc::borrow::Cow;
+use core::num::ParseIntError;
+use core::result::Result as StdResult;
 use snafu::{OptionExt, ResultExt};
-use std::borrow::Cow;
 use std::io::{self, BufRead};
-use std::num::ParseIntError;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the FaceSplit
 /// Parser.
diff --git a/src/run/parser/flitter/mod.rs b/src/run/parser/flitter/mod.rs
index 831f9c6..256a13b 100644
--- a/src/run/parser/flitter/mod.rs
+++ b/src/run/parser/flitter/mod.rs
@@ -1,9 +1,9 @@
 //! Provides the parser for Flitter splits files.
 
 use crate::{comparison::world_record, Run, Segment, Time, TimeSpan};
+use core::result::Result as StdResult;
 use serde::Deserialize;
 use std::io::BufRead;
-use std::result::Result as StdResult;
 
 mod s_expressions;
 
diff --git a/src/run/parser/flitter/s_expressions.rs b/src/run/parser/flitter/s_expressions.rs
index 43eb36a..d75e1a1 100644
--- a/src/run/parser/flitter/s_expressions.rs
+++ b/src/run/parser/flitter/s_expressions.rs
@@ -1,10 +1,10 @@
 //! Implements a serde deserializer for S-Expressions.
 //! http://people.csail.mit.edu/rivest/Sexp.txt
 
+use core::fmt::Display;
+use core::num::ParseIntError;
 use serde::de::{self, DeserializeOwned, DeserializeSeed, MapAccess, SeqAccess, Visitor};
-use std::fmt::Display;
 use std::io::{self, BufRead};
-use std::num::ParseIntError;
 use utf8::{BufReadDecoder, BufReadDecoderError};
 
 /// The Error types for splits files that couldn't be parsed by the Flitter
@@ -61,7 +61,7 @@ impl de::Error for Error {
     }
 }
 
-pub type Result<T> = std::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, Error>;
 
 pub struct Deserializer<B: BufRead> {
     decoder: BufReadDecoder<B>,
diff --git a/src/run/parser/livesplit.rs b/src/run/parser/livesplit.rs
index a174926..5b6fcf1 100644
--- a/src/run/parser/livesplit.rs
+++ b/src/run/parser/livesplit.rs
@@ -7,10 +7,10 @@ use crate::xml_util::{
 };
 use crate::{AtomicDateTime, Run, RunMetadata, Segment, Time, TimeSpan};
 use chrono::{DateTime, TimeZone, Utc};
+use core::str;
 use quick_xml::Reader;
 use std::io::BufRead;
 use std::path::PathBuf;
-use std::str;
 
 use crate::xml_util::Error as XmlError;
 use chrono::ParseError as ChronoError;
@@ -27,22 +27,22 @@ pub enum Error {
     /// Failed to decode a string slice as UTF-8.
     Utf8Str {
         /// The underlying error.
-        source: std::str::Utf8Error,
+        source: core::str::Utf8Error,
     },
     /// Failed to decode a string as UTF-8.
     Utf8String {
         /// The underlying error.
-        source: std::string::FromUtf8Error,
+        source: alloc::string::FromUtf8Error,
     },
     /// Failed to parse an integer.
     ParseInt {
         /// The underlying error.
-        source: std::num::ParseIntError,
+        source: core::num::ParseIntError,
     },
     /// Failed to parse a floating point number.
     ParseFloat {
         /// The underlying error.
-        source: std::num::ParseFloatError,
+        source: core::num::ParseFloatError,
     },
     /// Failed to parse a time.
     ParseTime {
@@ -64,7 +64,7 @@ pub enum Error {
 }
 
 /// The Result type for the LiveSplit Parser.
-pub type Result<T> = std::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, Error>;
 
 // FIXME: Generalized Type Ascription (GTA 6)
 #[inline]
diff --git a/src/run/parser/llanfair.rs b/src/run/parser/llanfair.rs
index 9efeec8..fbabe67 100644
--- a/src/run/parser/llanfair.rs
+++ b/src/run/parser/llanfair.rs
@@ -5,8 +5,8 @@ use byteorder::{ReadBytesExt, BE};
 use image::{png, ColorType, ImageBuffer, Rgba};
 use snafu::{OptionExt, ResultExt};
 use std::io::{self, Read, Seek, SeekFrom};
-use std::result::Result as StdResult;
-use std::str::{from_utf8, Utf8Error};
+use core::result::Result as StdResult;
+use core::str::{from_utf8, Utf8Error};
 
 /// The Error type for splits files that couldn't be parsed by the Llanfair
 /// Parser.
diff --git a/src/run/parser/llanfair2.rs b/src/run/parser/llanfair2.rs
index e0f3b26..db33556 100644
--- a/src/run/parser/llanfair2.rs
+++ b/src/run/parser/llanfair2.rs
@@ -7,7 +7,7 @@ use crate::{RealTime, Run, Segment, Time, TimeSpan};
 use byteorder::{ByteOrder, BE};
 use image::{png, ColorType, ImageBuffer, Rgba};
 use quick_xml::Reader;
-use std::cmp::min;
+use core::cmp::min;
 use std::io::BufRead;
 
 use crate::xml_util::Error as XmlError;
@@ -24,24 +24,24 @@ pub enum Error {
     /// Failed to decode a string slice as UTF-8.
     Utf8Str {
         /// The underlying error.
-        source: std::str::Utf8Error,
+        source: core::str::Utf8Error,
     },
     /// Failed to decode a string as UTF-8.
     Utf8String {
         /// The underlying error.
-        source: std::string::FromUtf8Error,
+        source: alloc::string::FromUtf8Error,
     },
     /// Failed to parse an integer.
     Int {
         /// The underlying error.
-        source: std::num::ParseIntError,
+        source: core::num::ParseIntError,
     },
     /// Failed to parse an image.
     Image,
 }
 
 /// The Result type for the Llanfair Rewrite Parser.
-pub type Result<T> = std::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, Error>;
 
 // FIXME: Generalized Type Ascription (GTA 6)
 #[inline]
diff --git a/src/run/parser/llanfair_gered.rs b/src/run/parser/llanfair_gered.rs
index 9007afb..2f9c6dc 100644
--- a/src/run/parser/llanfair_gered.rs
+++ b/src/run/parser/llanfair_gered.rs
@@ -26,17 +26,17 @@ pub enum Error {
     /// Failed to decode a string slice as UTF-8.
     Utf8Str {
         /// The underlying error.
-        source: std::str::Utf8Error,
+        source: core::str::Utf8Error,
     },
     /// Failed to decode a string as UTF-8.
     Utf8String {
         /// The underlying error.
-        source: std::string::FromUtf8Error,
+        source: alloc::string::FromUtf8Error,
     },
     /// Failed to parse an integer.
     Int {
         /// The underlying error.
-        source: std::num::ParseIntError,
+        source: core::num::ParseIntError,
     },
     /// The length of a buffer was too large.
     LengthOutOfBounds,
@@ -45,7 +45,7 @@ pub enum Error {
 }
 
 /// The Result type for the Llanfair (Gered) Parser.
-pub type Result<T> = std::result::Result<T, Error>;
+pub type Result<T> = core::result::Result<T, Error>;
 
 // FIXME: Generalized Type Ascription (GTA 6)
 #[inline]
diff --git a/src/run/parser/portal2_live_timer.rs b/src/run/parser/portal2_live_timer.rs
index 36dbec7..5a49383 100644
--- a/src/run/parser/portal2_live_timer.rs
+++ b/src/run/parser/portal2_live_timer.rs
@@ -1,10 +1,10 @@
 //! Provides the parser for Portal 2 Live Timer splits files.
 
 use crate::{GameTime, Run, Segment, TimeSpan};
+use core::num::ParseFloatError;
+use core::result::Result as StdResult;
 use snafu::{OptionExt, ResultExt};
 use std::io::{self, BufRead};
-use std::num::ParseFloatError;
-use std::result::Result as StdResult;
 
 /// The Error types for splits files that couldn't be parsed by the Portal 2
 /// Live Timer Parser.
diff --git a/src/run/parser/shit_split.rs b/src/run/parser/shit_split.rs
index 087c7c7..e56c2d9 100644
--- a/src/run/parser/shit_split.rs
+++ b/src/run/parser/shit_split.rs
@@ -1,10 +1,10 @@
 //! Provides the parser for ShitSplit splits files.
 
 use crate::{timing, GameTime, Run, Segment, TimeSpan};
+use core::num::ParseIntError;
+use core::result::Result as StdResult;
 use snafu::{OptionExt, ResultExt};
 use std::io::{self, BufRead};
-use std::num::ParseIntError;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the ShitSplit
 /// Parser.
diff --git a/src/run/parser/source_live_timer.rs b/src/run/parser/source_live_timer.rs
index 023e336..8a1d1ae 100644
--- a/src/run/parser/source_live_timer.rs
+++ b/src/run/parser/source_live_timer.rs
@@ -1,12 +1,12 @@
 //! Provides the parser for the SourceLiveTimer splits files.
 
 use crate::{GameTime, Run, Segment, TimeSpan};
+use core::result::Result as StdResult;
 use serde::Deserialize;
 use serde_json::de::from_reader;
 use serde_json::Error as JsonError;
 use snafu::ResultExt;
 use std::io::Read;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the
 /// SourceLiveTimer Parser.
diff --git a/src/run/parser/splits_io.rs b/src/run/parser/splits_io.rs
index 344bd24..2c119a0 100644
--- a/src/run/parser/splits_io.rs
+++ b/src/run/parser/splits_io.rs
@@ -1,12 +1,12 @@
 //! Provides the parser for generic Splits I/O splits files.
 
 use crate::{Run, Segment as LiveSplitSegment, Time, TimeSpan};
+use core::result::Result as StdResult;
 use serde::{Deserialize, Serialize};
 use serde_json::de::from_reader;
 use serde_json::Error as JsonError;
 use snafu::ResultExt;
 use std::io::Read;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the generic
 /// Splits I/O Parser.
diff --git a/src/run/parser/splitterz.rs b/src/run/parser/splitterz.rs
index 764ef5a..581631c 100644
--- a/src/run/parser/splitterz.rs
+++ b/src/run/parser/splitterz.rs
@@ -1,11 +1,11 @@
 //! Provides the parser for SplitterZ splits files.
 
 use crate::{settings::Image, timing, RealTime, Run, Segment, TimeSpan};
+use alloc::borrow::Cow;
+use core::num::ParseIntError;
+use core::result::Result as StdResult;
 use snafu::ResultExt;
-use std::borrow::Cow;
 use std::io::{self, BufRead};
-use std::num::ParseIntError;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the SplitterZ
 /// Parser.
diff --git a/src/run/parser/splitty.rs b/src/run/parser/splitty.rs
index 01727d2..722ba02 100644
--- a/src/run/parser/splitty.rs
+++ b/src/run/parser/splitty.rs
@@ -1,12 +1,12 @@
 //! Provides the parser for Splitty splits files.
 
 use crate::{Run, Segment, Time, TimeSpan, TimingMethod};
+use core::result::Result as StdResult;
 use serde::Deserialize;
 use serde_json::de::from_reader;
 use serde_json::Error as JsonError;
 use snafu::ResultExt;
 use std::io::Read;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the Splitty
 /// Parser.
diff --git a/src/run/parser/time_split_tracker.rs b/src/run/parser/time_split_tracker.rs
index ad3506e..fa46218 100644
--- a/src/run/parser/time_split_tracker.rs
+++ b/src/run/parser/time_split_tracker.rs
@@ -3,12 +3,12 @@
 use super::super::ComparisonError;
 use crate::{settings::Image, timing, AtomicDateTime, RealTime, Run, Segment, Time, TimeSpan};
 use chrono::{TimeZone, Utc};
+use core::num::ParseIntError;
+use core::result::Result as StdResult;
 use snafu::{OptionExt, ResultExt};
 use std::fs::File;
 use std::io::{self, BufRead, BufReader};
-use std::num::ParseIntError;
 use std::path::PathBuf;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the Time
 /// Split Tracker Parser.
diff --git a/src/run/parser/timer_kind.rs b/src/run/parser/timer_kind.rs
index b1c4e51..07f9e38 100644
--- a/src/run/parser/timer_kind.rs
+++ b/src/run/parser/timer_kind.rs
@@ -1,4 +1,4 @@
-use std::fmt;
+use core::fmt;
 
 /// Describes the different Timers available that store splits files.
 #[derive(Clone, Eq, PartialEq, Hash, Debug)]
diff --git a/src/run/parser/urn.rs b/src/run/parser/urn.rs
index 157656f..580b91c 100644
--- a/src/run/parser/urn.rs
+++ b/src/run/parser/urn.rs
@@ -1,12 +1,12 @@
 //! Provides the parser for Urn splits files.
 
 use crate::{Run, Segment, Time, TimeSpan};
+use core::result::Result as StdResult;
 use serde::Deserialize;
 use serde_json::de::from_reader;
 use serde_json::Error as JsonError;
 use snafu::ResultExt;
 use std::io::Read;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the Urn
 /// Parser.
diff --git a/src/run/parser/worstrun.rs b/src/run/parser/worstrun.rs
index 4e26467..26b13b5 100644
--- a/src/run/parser/worstrun.rs
+++ b/src/run/parser/worstrun.rs
@@ -1,12 +1,12 @@
 //! Provides the parser for worstrun splits files.
 
 use crate::{Run, Segment, Time, TimeSpan};
+use core::result::Result as StdResult;
 use serde::Deserialize;
 use serde_json::de::from_reader;
 use serde_json::Error as JsonError;
 use snafu::ResultExt;
 use std::io::Read;
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the worstrun
 /// Parser.
diff --git a/src/run/parser/wsplit.rs b/src/run/parser/wsplit.rs
index 6aace61..efef860 100644
--- a/src/run/parser/wsplit.rs
+++ b/src/run/parser/wsplit.rs
@@ -1,10 +1,10 @@
 //! Provides the parser for WSplit splits files.
 
 use crate::{settings::Image, RealTime, Run, Segment, TimeSpan};
+use core::num::{ParseFloatError, ParseIntError};
+use core::result::Result as StdResult;
 use snafu::ResultExt;
 use std::io::{self, BufRead};
-use std::num::{ParseFloatError, ParseIntError};
-use std::result::Result as StdResult;
 
 /// The Error type for splits files that couldn't be parsed by the WSplit
 /// Parser.
diff --git a/src/run/run_metadata.rs b/src/run/run_metadata.rs
index 411265d..1589935 100644
--- a/src/run/run_metadata.rs
+++ b/src/run/run_metadata.rs
@@ -1,5 +1,6 @@
-use indexmap::map::{IndexMap, Iter};
+use crate::indexmap::map::{IndexMap, Iter};
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// The Run Metadata stores additional information about a run, like the
 /// platform and region of the game. All of this information is optional.
diff --git a/src/run/saver/livesplit.rs b/src/run/saver/livesplit.rs
index e98cc2d..afe6f8d 100644
--- a/src/run/saver/livesplit.rs
+++ b/src/run/saver/livesplit.rs
@@ -26,15 +26,15 @@
 
 use crate::timing::formatter::{Complete, TimeFormatter};
 use crate::{settings::Image, Run, Time, TimeSpan, Timer, TimerPhase};
+use alloc::borrow::Cow;
 use byteorder::{WriteBytesExt, LE};
 use chrono::{DateTime, Utc};
+use core::fmt::Display;
+use core::mem::replace;
+use core::result::Result as StdResult;
 use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
 use quick_xml::{Error as XmlError, Writer};
-use std::borrow::Cow;
-use std::fmt::Display;
 use std::io::Write;
-use std::mem::replace;
-use std::result::Result as StdResult;
 
 static LSS_IMAGE_HEADER: &[u8; 156] = include_bytes!("lss_image_header.bin");
 
diff --git a/src/run/segment.rs b/src/run/segment.rs
index ff02d89..b3bf242 100644
--- a/src/run/segment.rs
+++ b/src/run/segment.rs
@@ -1,6 +1,7 @@
 use crate::comparison::personal_best;
 use crate::{settings::Image, SegmentHistory, Time, TimeSpan, TimingMethod};
-use std::collections::HashMap;
+use hashbrown::HashMap;
+use crate::platform::prelude::*;
 
 /// A Segment describes a point in a speedrun that is suitable for storing a
 /// split time. This stores the name of that segment, an icon, the split times
diff --git a/src/run/segment_history.rs b/src/run/segment_history.rs
index 0e14a2d..61145a1 100644
--- a/src/run/segment_history.rs
+++ b/src/run/segment_history.rs
@@ -1,6 +1,7 @@
 use crate::Time;
-use std::cmp::min;
-use std::slice::{Iter, IterMut};
+use core::cmp::min;
+use core::slice::{Iter, IterMut};
+use crate::platform::prelude::*;
 
 /// Stores the segment times achieved for a certain segment. Each segment is
 /// tagged with an index. Only segment times with an index larger than 0 are
diff --git a/src/settings/color.rs b/src/settings/color.rs
index 27d9129..5b6d66b 100644
--- a/src/settings/color.rs
+++ b/src/settings/color.rs
@@ -1,4 +1,4 @@
-use palette::{Hsla, LinSrgba, Pixel};
+use crate::palette::{Hsla, LinSrgba, Pixel};
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 
 /// Colors can be used to describe what color to use for visualizing
diff --git a/src/settings/field.rs b/src/settings/field.rs
index fc58d7c..b6f87ff 100644
--- a/src/settings/field.rs
+++ b/src/settings/field.rs
@@ -1,5 +1,6 @@
 use super::Value;
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// A Field describes a single setting by its name and its current value.
 #[derive(Serialize, Deserialize)]
diff --git a/src/settings/image/mod.rs b/src/settings/image/mod.rs
index 0187e50..823a2a8 100644
--- a/src/settings/image/mod.rs
+++ b/src/settings/image/mod.rs
@@ -1,14 +1,12 @@
+use crate::platform::prelude::*;
 use base64::{display::Base64Display, STANDARD};
+use core::sync::atomic::{AtomicUsize, Ordering};
 use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
-use std::fs::File;
-use std::io::{self, Read};
-use std::path::Path;
-use std::sync::atomic::{AtomicUsize, Ordering};
 
 #[cfg(test)]
 mod tests;
 
-#[cfg(feature = "image-shrinking")]
+#[cfg(all(feature = "std", feature = "image-shrinking"))]
 mod shrinking;
 
 static LAST_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
@@ -112,12 +110,15 @@ impl Image {
 
     /// Loads an image from the file system. You need to provide a buffer used
     /// for temporarily storing the image's data.
-    pub fn from_file<P, B>(path: P, mut buf: B) -> io::Result<Image>
+    #[cfg(feature = "std")]
+    pub fn from_file<P, B>(path: P, mut buf: B) -> std::io::Result<Image>
     where
-        P: AsRef<Path>,
+        P: AsRef<std::path::Path>,
         B: AsMut<Vec<u8>>,
     {
-        let mut file = File::open(path)?;
+        use std::io::Read;
+
+        let mut file = std::fs::File::open(path)?;
         let len = file.metadata()?.len() as usize;
 
         let buf = buf.as_mut();
@@ -153,13 +154,33 @@ impl Image {
     /// Modifies an image by replacing its image data with the new image data
     /// provided. The image's ID changes to a new unique ID.
     pub fn modify(&mut self, data: &[u8]) {
-        #[cfg(feature = "image-shrinking")]
+        #[cfg(all(feature = "std", feature = "image-shrinking"))]
         let data = {
             const MAX_IMAGE_SIZE: u32 = 128;
 
             shrinking::shrink(data, MAX_IMAGE_SIZE)
         };
-        self.id = LAST_IMAGE_ID.fetch_add(1, Ordering::Relaxed);
+        cfg_if::cfg_if! {
+            // FIXME: Some targets don't have atomics, but we can't test for
+            // this properly yet. So there's a feature you explicitly have to
+            // opt into to deactivate the usage of atomics. Vice versa would be
+            // too dangerous, as `default-features = false` would deactivate
+            // atomics then. However there's also `cargo test --all-features`,
+            // which is equally dangerous. To detect this, we have an additional
+            // `internal-use-all-features` feature that is only ever activated
+            // when `--all-features` is passed, so we can ignore the
+            // `doesnt-have-atomics` in that case.
+            // https://github.com/rust-lang/rust/issues/32976
+            if #[cfg(any(
+                not(feature = "doesnt-have-atomics"),
+                feature = "internal-use-all-features",
+            ))] {
+                self.id = LAST_IMAGE_ID.fetch_add(1, Ordering::Relaxed);
+            } else {
+                self.id = LAST_IMAGE_ID.load(Ordering::SeqCst) + 1;
+                LAST_IMAGE_ID.store(self.id, Ordering::SeqCst);
+            }
+        }
         self.data.clear();
         self.data.extend_from_slice(&*data);
     }
diff --git a/src/settings/image/shrinking.rs b/src/settings/image/shrinking.rs
index 739225e..fb8b45c 100644
--- a/src/settings/image/shrinking.rs
+++ b/src/settings/image/shrinking.rs
@@ -2,7 +2,7 @@ use image::{
     bmp, gif, guess_format, hdr, ico, jpeg, load_from_memory_with_format, png, pnm, tiff, webp,
     DynamicImage, ImageDecoder, ImageError, ImageFormat,
 };
-use std::borrow::Cow;
+use alloc::borrow::Cow;
 use std::io::Cursor;
 
 fn shrink_inner(data: &[u8], max_dim: u32) -> Result<Cow<'_, [u8]>, ImageError> {
diff --git a/src/settings/settings_description.rs b/src/settings/settings_description.rs
index bd25e0d..20e9581 100644
--- a/src/settings/settings_description.rs
+++ b/src/settings/settings_description.rs
@@ -1,5 +1,6 @@
 use super::Field;
 use serde::{Deserialize, Serialize};
+use crate::platform::prelude::*;
 
 /// A generic description of the settings available and their current values.
 #[derive(Default, Serialize, Deserialize)]
diff --git a/src/settings/value.rs b/src/settings/value.rs
index 18b2c9e..37c55d2 100644
--- a/src/settings/value.rs
+++ b/src/settings/value.rs
@@ -6,8 +6,9 @@ use crate::{
     timing::formatter::{Accuracy, DigitsFormat},
     TimingMethod,
 };
+use core::result::Result as StdResult;
 use serde::{Deserialize, Serialize};
-use std::result::Result as StdResult;
+use crate::platform::prelude::*;
 
 /// Describes a setting's value. Such a value can be of a variety of different
 /// types.
diff --git a/src/timing/atomic_date_time.rs b/src/timing/atomic_date_time.rs
index 1efeb9c..56866ae 100644
--- a/src/timing/atomic_date_time.rs
+++ b/src/timing/atomic_date_time.rs
@@ -1,7 +1,7 @@
 use crate::platform::utc_now;
 use crate::TimeSpan;
-use chrono::{DateTime, Utc};
-use std::ops::Sub;
+use crate::platform::{DateTime, Utc};
+use core::ops::Sub;
 
 /// An Atomic Date Time represents a UTC Date Time that tries to be as close to
 /// an atomic clock as possible.
diff --git a/src/timing/formatter/accuracy.rs b/src/timing/formatter/accuracy.rs
index 99668ee..afd7e36 100644
--- a/src/timing/formatter/accuracy.rs
+++ b/src/timing/formatter/accuracy.rs
@@ -28,7 +28,7 @@ impl Accuracy {
 }
 
 use super::{extract_hundredths, extract_milliseconds, extract_tenths};
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 #[derive(Debug, PartialEq, Copy, Clone)]
 pub struct FormattedSeconds {
diff --git a/src/timing/formatter/complete.rs b/src/timing/formatter/complete.rs
index 5322819..650b0f1 100644
--- a/src/timing/formatter/complete.rs
+++ b/src/timing/formatter/complete.rs
@@ -1,6 +1,6 @@
 use super::{TimeFormatter, ASCII_MINUS};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 pub struct Inner(Option<TimeSpan>);
 
diff --git a/src/timing/formatter/days.rs b/src/timing/formatter/days.rs
index ff0d798..5df90a0 100644
--- a/src/timing/formatter/days.rs
+++ b/src/timing/formatter/days.rs
@@ -1,6 +1,6 @@
 use super::{TimeFormatter, MINUS};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 pub struct Inner {
     time: Option<TimeSpan>,
diff --git a/src/timing/formatter/delta.rs b/src/timing/formatter/delta.rs
index a8bd365..b891689 100644
--- a/src/timing/formatter/delta.rs
+++ b/src/timing/formatter/delta.rs
@@ -1,6 +1,6 @@
 use super::{Accuracy, TimeFormatter, DASH, MINUS, PLUS};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 pub struct Inner {
     time: Option<TimeSpan>,
diff --git a/src/timing/formatter/mod.rs b/src/timing/formatter/mod.rs
index e400ef3..abb501f 100644
--- a/src/timing/formatter/mod.rs
+++ b/src/timing/formatter/mod.rs
@@ -41,8 +41,10 @@ pub use self::regular::Regular;
 pub use self::short::Short;
 
 use crate::TimeSpan;
-use std::cmp::min;
-use std::fmt::Display;
+use core::cmp::min;
+use core::fmt::Display;
+#[cfg(not(feature = "std"))]
+use libm::F64Ext;
 
 /// Time Formatters can be used to format optional Time Spans in various ways.
 pub trait TimeFormatter<'a> {
diff --git a/src/timing/formatter/none_wrapper.rs b/src/timing/formatter/none_wrapper.rs
index 74e5150..e04d587 100644
--- a/src/timing/formatter/none_wrapper.rs
+++ b/src/timing/formatter/none_wrapper.rs
@@ -4,7 +4,7 @@
 
 use super::{TimeFormatter, DASH};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 /// A Time Span to be formatted by a None Wrapper.
 pub struct Inner<'a, F, S> {
diff --git a/src/timing/formatter/possible_time_save.rs b/src/timing/formatter/possible_time_save.rs
index 81dead8..eb1a075 100644
--- a/src/timing/formatter/possible_time_save.rs
+++ b/src/timing/formatter/possible_time_save.rs
@@ -1,6 +1,6 @@
 use super::{Accuracy, TimeFormatter, DASH, MINUS};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 pub struct Inner {
     time: Option<TimeSpan>,
diff --git a/src/timing/formatter/regular.rs b/src/timing/formatter/regular.rs
index 1ea595c..1905b0d 100644
--- a/src/timing/formatter/regular.rs
+++ b/src/timing/formatter/regular.rs
@@ -1,6 +1,6 @@
 use super::{Accuracy, TimeFormatter, DASH, MINUS};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 pub struct Inner {
     time: Option<TimeSpan>,
diff --git a/src/timing/formatter/short.rs b/src/timing/formatter/short.rs
index aeb492a..9cb28c7 100644
--- a/src/timing/formatter/short.rs
+++ b/src/timing/formatter/short.rs
@@ -1,6 +1,6 @@
 use super::{Accuracy, TimeFormatter, MINUS};
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 pub struct Inner {
     time: Option<TimeSpan>,
diff --git a/src/timing/formatter/timer.rs b/src/timing/formatter/timer.rs
index a8dd987..c9a749b 100644
--- a/src/timing/formatter/timer.rs
+++ b/src/timing/formatter/timer.rs
@@ -7,7 +7,7 @@ use super::{
     TimeFormatter, MINUS,
 };
 use crate::TimeSpan;
-use std::fmt::{Display, Formatter, Result};
+use core::fmt::{Display, Formatter, Result};
 
 /// A Time Span to be formatted as the main part of the Time Formatter Pair.
 pub struct TimeInner {
diff --git a/src/timing/mod.rs b/src/timing/mod.rs
index 7deb6f8..8df5ea3 100644
--- a/src/timing/mod.rs
+++ b/src/timing/mod.rs
@@ -14,6 +14,8 @@ pub use self::atomic_date_time::AtomicDateTime;
 pub use self::time::{GameTime, RealTime, Time};
 pub use self::time_span::{ParseError, TimeSpan};
 pub use self::time_stamp::TimeStamp;
-pub use self::timer::{CreationError as TimerCreationError, SharedTimer, Timer};
+#[cfg(feature = "std")]
+pub use self::timer::SharedTimer;
+pub use self::timer::{CreationError as TimerCreationError, Timer};
 pub use self::timer_phase::TimerPhase;
 pub use self::timing_method::TimingMethod;
diff --git a/src/timing/time.rs b/src/timing/time.rs
index 8860bb1..0e0c66f 100644
--- a/src/timing/time.rs
+++ b/src/timing/time.rs
@@ -1,5 +1,5 @@
 use crate::{TimeSpan, TimingMethod};
-use std::ops::{Add, AddAssign, Index, IndexMut, Sub, SubAssign};
+use core::ops::{Add, AddAssign, Index, IndexMut, Sub, SubAssign};
 
 /// A time that can store a Real Time and a Game Time. Both of them are
 /// optional.
diff --git a/src/timing/time_span.rs b/src/timing/time_span.rs
index 6d7c8e9..9b9287e 100644
--- a/src/timing/time_span.rs
+++ b/src/timing/time_span.rs
@@ -1,10 +1,10 @@
-use crate::platform::Duration as StdDuration;
-use chrono::Duration;
+use crate::platform::Duration;
+use core::num::ParseFloatError;
+use core::ops::{AddAssign, SubAssign};
+use core::str::FromStr;
 use derive_more::{Add, From, Neg, Sub};
 use snafu::ResultExt;
-use std::num::ParseFloatError;
-use std::ops::{AddAssign, SubAssign};
-use std::str::FromStr;
+use crate::platform::prelude::*;
 
 /// A Time Span represents a certain span of time.
 #[derive(From, Add, Sub, Neg, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
@@ -105,8 +105,8 @@ impl Default for TimeSpan {
     }
 }
 
-impl From<StdDuration> for TimeSpan {
-    fn from(duration: StdDuration) -> Self {
+impl From<core::time::Duration> for TimeSpan {
+    fn from(duration: core::time::Duration) -> Self {
         TimeSpan(Duration::from_std(duration).unwrap())
     }
 }
@@ -123,8 +123,8 @@ impl SubAssign for TimeSpan {
     }
 }
 
+use core::fmt;
 use serde::de::{self, Deserialize, Deserializer, Visitor};
-use std::fmt;
 
 impl<'de> Deserialize<'de> for TimeSpan {
     fn deserialize<D>(deserializer: D) -> Result<TimeSpan, D::Error>
diff --git a/src/timing/time_stamp.rs b/src/timing/time_stamp.rs
index e976e99..f61bb80 100644
--- a/src/timing/time_stamp.rs
+++ b/src/timing/time_stamp.rs
@@ -1,6 +1,6 @@
 use crate::platform::Instant;
 use crate::TimeSpan;
-use std::ops::Sub;
+use core::ops::Sub;
 
 /// A Time Stamp stores a point in time, that can be used to calculate Time
 /// Spans.
diff --git a/src/timing/timer/mod.rs b/src/timing/timer/mod.rs
index 3d0baf5..266cc81 100644
--- a/src/timing/timer/mod.rs
+++ b/src/timing/timer/mod.rs
@@ -1,9 +1,8 @@
 use crate::comparison::personal_best;
+use crate::platform::prelude::*;
 use crate::TimerPhase::*;
 use crate::{AtomicDateTime, Run, Segment, Time, TimeSpan, TimeStamp, TimerPhase, TimingMethod};
-use parking_lot::RwLock;
-use std::mem;
-use std::sync::Arc;
+use core::mem;
 
 #[cfg(test)]
 mod tests;
@@ -61,7 +60,8 @@ pub struct Timer {
 
 /// A Shared Timer is a wrapper around the Timer that can be shared across
 /// multiple threads with multiple owners.
-pub type SharedTimer = Arc<RwLock<Timer>>;
+#[cfg(feature = "std")]
+pub type SharedTimer = alloc::sync::Arc<parking_lot::RwLock<Timer>>;
 
 /// The Error type for creating a new Timer from a Run.
 #[derive(Debug, snafu::Snafu)]
@@ -104,8 +104,9 @@ impl Timer {
 
     /// Consumes the Timer and creates a Shared Timer that can be shared across
     /// multiple threads with multiple owners.
+    #[cfg(feature = "std")]
     pub fn into_shared(self) -> SharedTimer {
-        Arc::new(RwLock::new(self))
+        alloc::sync::Arc::new(parking_lot::RwLock::new(self))
     }
 
     /// Takes out the Run from the Timer and resets the current attempt if there
diff --git a/src/xml_util.rs b/src/xml_util.rs
index b48e579..1115f6e 100644
--- a/src/xml_util.rs
+++ b/src/xml_util.rs
@@ -1,12 +1,13 @@
 use crate::timing;
+use alloc::borrow::Cow;
+use alloc::string;
 use chrono::ParseError as ChronoError;
+use core::num::{ParseFloatError, ParseIntError};
+use core::ops::Deref;
+use core::str;
 use quick_xml::events::{attributes, BytesStart, Event};
 use quick_xml::{Error as XmlError, Reader, Writer};
-use std::borrow::Cow;
 use std::io::{self, BufRead};
-use std::num::{ParseFloatError, ParseIntError};
-use std::ops::Deref;
-use std::{str, string};
 
 /// The Error type for XML-based splits files that couldn't be parsed.
 #[derive(Debug, snafu::Snafu, derive_more::From)]


Test patch:
diff --git a/.github/workflows/test.sh b/.github/workflows/test.sh
index 35f602a..01be2de 100644
--- a/.github/workflows/test.sh
+++ b/.github/workflows/test.sh
@@ -7,7 +7,7 @@ main() {
     fi
 
     $cargo test -p livesplit-core --all-features --target $TARGET
-    $cargo test -p livesplit-core --no-default-features --target $TARGET
+    $cargo test -p livesplit-core --no-default-features --features std --target $TARGET
 }
 
 main
diff --git a/crates/no-std-test/Cargo.toml b/crates/no-std-test/Cargo.toml
new file mode 100644
index 0000000..7b16a8a
--- /dev/null
+++ b/crates/no-std-test/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "no-std-test"
+version = "0.1.0"
+authors = ["Christopher Serr <christopher.serr@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[workspace]
+
+[dependencies]
+livesplit-core = { path = "../..", default-features = false }
+
+[features]
+doesnt-have-atomics = ["livesplit-core/doesnt-have-atomics"]
diff --git a/crates/no-std-test/README.md b/crates/no-std-test/README.md
new file mode 100644
index 0000000..5482e37
--- /dev/null
+++ b/crates/no-std-test/README.md
@@ -0,0 +1,6 @@
+# no_std Testing
+
+In order to test the build for a no_std target, we can't use the same workspace
+as the main one as that one includes std-only dev-dependencies that leak their
+features into the no_std main dependencies. This is a cargo bug. This crate is
+therefore only a temporary workaround.
diff --git a/crates/no-std-test/src/lib.rs b/crates/no-std-test/src/lib.rs
new file mode 100644
index 0000000..0c9ac1a
--- /dev/null
+++ b/crates/no-std-test/src/lib.rs
@@ -0,0 +1,1 @@
+#![no_std]