├── rustfmt.toml ├── .gitignore ├── savefile_abi_example ├── app │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── interface_crate │ ├── .gitignore │ ├── src │ │ └── lib.rs │ └── Cargo.toml └── implementation_crate │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── lib.rs ├── release.toml ├── compile_tests ├── src │ └── main.rs ├── tests │ ├── run-pass │ │ └── do_nothing.rs │ ├── compile-fail │ │ ├── abi_ref_return.rs │ │ ├── method_with_lifetimes.rs │ │ ├── missing_self.rs │ │ ├── no_return_ref_str.rs │ │ ├── abi_async.rs │ │ ├── no_impl_trait.rs │ │ ├── no_mut_data_ref.rs │ │ ├── no_ref_box.rs │ │ ├── no_ref_ref.rs │ │ ├── no_mutable_fn.rs │ │ ├── abi_impl_fut.rs │ │ ├── no_unmutable_fnmut.rs │ │ ├── async_trait.rs │ │ ├── bad_export2.rs │ │ ├── bad_export3.rs │ │ ├── bad_export.rs │ │ └── cow_smuggler.rs │ └── tests.rs └── Cargo.toml ├── savefile-test ├── .gitignore ├── fuzz │ ├── .gitignore │ ├── Cargo.toml │ └── fuzz_targets │ │ └── fuzz_target_1.rs ├── build.rs ├── src │ ├── savefile_abi_test.rs │ ├── test_arrayvec.rs │ ├── test_recursive_types.rs │ ├── test_nested_non_repr_c.rs │ ├── test_bounds.rs │ ├── cycles.rs │ ├── test_generic.rs │ ├── test_nested_repr_c.rs │ ├── savefile_abi_test │ │ ├── snapshots │ │ │ └── savefile_test__savefile_abi_test__argument_backward_compatibility__abi_schemas_get_def.snap │ │ ├── enum_tests.rs │ │ ├── test_different_trait_definitions.rs │ │ ├── closure_tests.rs │ │ ├── advanced_datatypes_test.rs │ │ ├── argument_backward_compatibility.rs │ │ └── basic_abi_tests.rs │ ├── test_more_async.rs │ ├── enum_variant_versioning.rs │ ├── test_enum_many_variants.rs │ ├── ext_benchmark.rs │ ├── test_versioning.rs │ ├── snapshots │ │ └── savefile_test__test_recursive_types__get_recursive_schema.snap │ └── test_introspect.rs ├── schemas │ ├── savefile_ArgInterfaceV2_0.schema │ └── savefile_ArgInterfaceV2_1.schema └── Cargo.toml ├── .cargo └── mutants.toml ├── savefile ├── .gitignore ├── build.rs ├── src │ └── prelude.rs └── Cargo.toml ├── savefile-min-build ├── Cargo.toml └── src │ └── lib.rs ├── savefile-abi-min ├── Cargo.toml └── src │ └── main.rs ├── savefile-abi-min-lib-impl ├── Cargo.toml └── src │ └── lib.rs ├── savefile-abi-min-lib ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── .github ├── dependabot.yml └── workflows │ ├── release-plz.yml │ └── rust.yml ├── release-plz.toml ├── savefile-derive ├── Cargo.toml └── src │ ├── serialize.rs │ └── deserialize.rs ├── savefile-abi ├── Cargo.toml ├── README.md ├── async.md └── src │ └── bytes.rs ├── LICENSE-MIT └── README.md /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | 4 | 5 | -------------------------------------------------------------------------------- /savefile_abi_example/app/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | exclude-paths=["savefile-test"] 2 | 3 | -------------------------------------------------------------------------------- /compile_tests/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | pub fn main() { 3 | } 4 | -------------------------------------------------------------------------------- /savefile-test/.gitignore: -------------------------------------------------------------------------------- 1 | test*.bin 2 | .idea 3 | 4 | 5 | -------------------------------------------------------------------------------- /compile_tests/tests/run-pass/do_nothing.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | } -------------------------------------------------------------------------------- /savefile_abi_example/interface_crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /savefile_abi_example/implementation_crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /savefile-test/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /.cargo/mutants.toml: -------------------------------------------------------------------------------- 1 | exclude_globs = ["savefile-test/src/*.rs"] 2 | 3 | 4 | -------------------------------------------------------------------------------- /savefile/.gitignore: -------------------------------------------------------------------------------- 1 | newersave.bin 2 | newsave.bin 3 | save.bin 4 | Cargo.lock 5 | 6 | -------------------------------------------------------------------------------- /savefile_abi_example/interface_crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | use savefile_derive::savefile_abi_exportable; 2 | 3 | #[savefile_abi_exportable(version=0)] 4 | pub trait AdderInterface { 5 | fn add(&self, x: u32, y: u32) -> u32; 6 | } 7 | -------------------------------------------------------------------------------- /savefile-test/build.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_version; 2 | use rustc_version::{version_meta, Channel}; 3 | fn main() { 4 | if version_meta().unwrap().channel == Channel::Nightly { 5 | println!("cargo:rustc-cfg=feature=\"nightly\""); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | mod basic_abi_tests; 4 | 5 | mod test_different_trait_definitions; 6 | 7 | mod argument_backward_compatibility; 8 | 9 | mod advanced_datatypes_test; 10 | mod closure_tests; 11 | 12 | mod enum_tests; 13 | -------------------------------------------------------------------------------- /savefile/build.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_version; 2 | use rustc_version::{version_meta, Channel}; 3 | fn main() { 4 | let version = version_meta().unwrap(); 5 | if version.channel == Channel::Nightly { 6 | println!("cargo:rustc-cfg=feature=\"nightly\""); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /savefile_abi_example/interface_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interface_crate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | savefile-abi.path = "../../savefile-abi" 8 | savefile.path = "../../savefile" 9 | savefile-derive.path= "../../savefile-derive" 10 | 11 | -------------------------------------------------------------------------------- /savefile_abi_example/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | savefile-abi.path = "../../savefile-abi" 8 | savefile.path = "../../savefile" 9 | savefile-derive.path= "../../savefile-derive" 10 | interface_crate = { path = "../interface_crate" } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /savefile_abi_example/implementation_crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "implementation_crate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | savefile-abi.path = "../../savefile-abi" 11 | savefile.path = "../../savefile" 12 | savefile-derive.path= "../../savefile-derive" 13 | interface_crate = { path = "../interface_crate" } 14 | 15 | -------------------------------------------------------------------------------- /savefile-test/schemas/savefile_ArgInterfaceV2_0.schema: -------------------------------------------------------------------------------- 1 | savefileArgInterfaceV2sums ArgArgumentdata1data2 ArgArgumentdata1data2enum_arg  EnumArgumentVariant1Variant2 -------------------------------------------------------------------------------- /savefile-min-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-min-build" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | savefile = { path = "../savefile", default-features = false, features = ["derive"] } 10 | savefile-abi = { path = "../savefile-abi" } 11 | async-trait = "0.1.83" 12 | -------------------------------------------------------------------------------- /savefile-min-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | use savefile::prelude::Savefile; 2 | use std::marker::PhantomData; 3 | 4 | #[derive(Savefile, Debug, PartialEq)] 5 | #[repr(u32)] 6 | #[savefile_doc_hidden] 7 | pub enum Example { 8 | A(u32, u32), 9 | B { a: u32, b: u32, c: u32 }, 10 | } 11 | 12 | /*#[derive(Debug, Savefile, PartialEq)] 13 | pub enum TestStructEnum { 14 | Variant2 { a: u8, b: u8 }, 15 | } 16 | */ 17 | #[test] 18 | fn test() {} 19 | -------------------------------------------------------------------------------- /savefile-test/schemas/savefile_ArgInterfaceV2_1.schema: -------------------------------------------------------------------------------- 1 | savefileArgInterfaceV2sums ArgArgumentdata2data3 ArgArgumentdata2data3enum_arg  EnumArgumentVariant1Variant2Variant3 -------------------------------------------------------------------------------- /savefile-abi-min/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-abi-min" 3 | version = "0.1.0" 4 | edition = "2021" 5 | rust-version = "1.74" 6 | 7 | 8 | 9 | [dependencies] 10 | savefile = { path = "../savefile", features = ["derive"], default-features = false } 11 | savefile-abi = { path = "../savefile-abi" } 12 | byteorder = "1.4" 13 | savefile-derive = {path = "../savefile-derive"} 14 | savefile-abi-min-lib = { path = "../savefile-abi-min-lib" } 15 | tokio="1.47" 16 | -------------------------------------------------------------------------------- /savefile-abi-min-lib-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-abi-min-lib-impl" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | savefile = { path = "../savefile", features = ["derive"], default-features = false } 11 | savefile-abi = { path = "../savefile-abi" } 12 | savefile-derive = {path = "../savefile-derive"} 13 | savefile-abi-min-lib = {path = "../savefile-abi-min-lib"} 14 | bytes = "1.8.0" 15 | -------------------------------------------------------------------------------- /savefile-abi-min-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-abi-min-lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | savefile = { path = "../savefile", features = ["derive"], default-features = false } 9 | savefile-abi = { path = "../savefile-abi" } 10 | savefile-derive = {path = "../savefile-derive"} 11 | tokio = { version = "1.47", features=["macros", "rt-multi-thread", "time"] } 12 | tokio-util="0.7" 13 | async-trait = "=0.1.83" 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "savefile", 4 | "savefile-derive", 5 | "savefile-test", 6 | "savefile-min-build", 7 | "savefile-abi-min", 8 | "savefile-abi", 9 | "savefile-abi-min-lib", 10 | "savefile-abi-min-lib-impl" 11 | ] 12 | exclude = ["compile_tests", "savefile_abi_example"] 13 | resolver = "2" 14 | 15 | [workspace.package] 16 | version = "0.20.2" 17 | 18 | [profile.dev.package] 19 | insta.opt-level = 3 20 | similar.opt-level = 3 21 | 22 | -------------------------------------------------------------------------------- /savefile_abi_example/implementation_crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | use interface_crate::{AdderInterface}; 2 | use savefile_derive::savefile_abi_export; 3 | 4 | #[derive(Default)] 5 | pub struct MyAdder { } 6 | 7 | impl AdderInterface for MyAdder { 8 | fn add(&self, x: u32, y: u32) -> u32 { 9 | x + y 10 | } 11 | } 12 | 13 | // Export this implementation as the default-implementation for 14 | // the interface 'AdderInterface', for the current library. 15 | savefile_abi_export!(MyAdder, AdderInterface); 16 | -------------------------------------------------------------------------------- /compile_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "compile_tests" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | savefile = { path = "../savefile", features = ["derive"], default-features = false } 10 | savefile-abi = { path = "../savefile-abi" } 11 | savefile-derive = { path = "../savefile-derive" } 12 | async-trait = "0.1" 13 | 14 | [dev-dependencies] 15 | compiletest_rs = { version = "0.11", features = [ "tmp" ] } 16 | 17 | [features] 18 | rustc = ["compiletest_rs/rustc"] 19 | assembly = [] 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /savefile_abi_example/app/src/main.rs: -------------------------------------------------------------------------------- 1 | use savefile_abi::{AbiConnection}; 2 | use interface_crate::{AdderInterface}; 3 | 4 | 5 | fn main() { 6 | // Load the implementation of `dyn AdderInterface` that was published 7 | // using the `savefile_abi_export!` above. 8 | let connection = AbiConnection:: 9 | ::load_shared_library("./ImplementationCrate.so").unwrap(); 10 | 11 | // The type `AbiConnection::` implements 12 | // the `AdderInterface`-trait, so we can use it to call its methods. 13 | assert_eq!(connection.add(1, 2), 3); 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /savefile-test/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "savefile-test-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.3" 14 | savefile = { path = "../../savefile", features = ["size_sanity_checks"] } 15 | savefile-derive = { path = "../../savefile-derive" } 16 | 17 | [dependencies.savefile-test] 18 | path = ".." 19 | 20 | # Prevent this from interfering with workspaces 21 | [workspace] 22 | members = ["."] 23 | 24 | [[bin]] 25 | name = "fuzz_target_1" 26 | path = "fuzz_targets/fuzz_target_1.rs" 27 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/abi_ref_return.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn set(&mut self, x: u32) -> &u32; 15 | //~^ 14:34: 14:35: Method 'set': savefile-abi does not support methods returning references. 16 | } 17 | 18 | fn main() {} -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/method_with_lifetimes.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn global_func<'a>(&self, x: &'a u32) -> u32; 15 | //~^ 14:20: 14:22: savefile-abi does not support methods with lifetimes. 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/missing_self.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn global_func(x: u32) -> u32; 15 | //~^ 14:20: 14:21: Method 'global_func' must have 'self'-parameter (savefile-abi does not support methods without self) 16 | } 17 | 18 | fn main() {} -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_return_ref_str.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn get_str(&self) -> &str; 15 | //~^ 14:26: 14:27: Method 'get_str': savefile-abi does not support methods returning &str. Use "String" or "&'static str" instead 16 | } 17 | 18 | fn main() {} -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/abi_async.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | async fn set(&mut self, x: u32) -> u32; 15 | //~^ 14:5: 14:10: savefile-abi does not support async methods. You can try returning a boxed future instead: Pin>> 16 | } 17 | 18 | fn main() {} -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_impl_trait.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn example_func(&self, x: impl Fn()); 15 | //~^ 14:31: 14:35: Method 'example_func', argument x, impl trait is not supported by savefile-abi. Try using a box: Box. 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_mut_data_ref.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn example_func(&self, x: &mut u32); 15 | //~^ 14:36: 14:39: Method 'example_func', argument x: Mutable references are not supported by savefile-abi (except for trait objects) 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_ref_box.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn example_func(&self, x: &Box); 15 | //~^ 14:32: 14:35: Savefile does not support reference to Box. This is also generally not very useful, just use a regular reference for arguments. 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_ref_ref.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn example_func(&self, x: &&u32); 15 | //~^ 14:32: 14:33: Method 'example_func', argument x: Method arguments cannot be reference to reference in savefile-abi. Try removing a '&' from the type: & u32 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release Plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Rust toolchain 22 | uses: dtolnay/rust-toolchain@stable 23 | - name: Run release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 28 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_mutable_fn.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn example_func(&self, x: &mut dyn Fn()); 15 | //~^ 14:40: 14:42: Method 'example_func', argument x: Mutable references to Fn are not supported by savefile-abi. Try using a non-mutable reference instead. 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/abi_impl_fut.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn set(&mut self, x: u32) -> impl Future; 15 | //~^ 14:39: 14:45: In return value of method 'set', impl Future is not supported by savefile-abi. You can try using Pin>> instead. 16 | } 17 | 18 | fn main() {} -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/no_unmutable_fnmut.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait ExampleTrait { 14 | fn example_func(&self, x: &dyn FnMut()); 15 | //~^ 14:36: 14:41: Method 'example_func', argument x: When using FnMut, it must be referenced using &mut or Box<..>, not &. Otherwise, it is impossible to call. 16 | } 17 | 18 | fn main() {} 19 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/async_trait.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | extern crate async_trait; 5 | use std::collections::HashMap; 6 | use savefile::prelude::*; 7 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 8 | use std::fmt::Debug; 9 | use std::io::{BufWriter, Cursor, Write}; 10 | use savefile_abi::AbiConnection; 11 | use savefile_derive::savefile_abi_exportable; 12 | 13 | #[savefile_abi_exportable(version = 0)] 14 | #[async_trait] 15 | //~^ 14:3: 14:14: async_trait-attribute macro detected. The #[async_trait] macro must go _before_ the #[savefile_abi_exportable(..)] macro! 16 | pub trait ExampleTrait { 17 | async fn set(&mut self, x: u32) -> u32; 18 | } 19 | 20 | fn main() {} -------------------------------------------------------------------------------- /savefile-test/fuzz/fuzz_targets/fuzz_target_1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | extern crate savefile; 5 | #[macro_use] 6 | extern crate savefile_derive; 7 | use savefile::prelude::*; 8 | 9 | #[derive(Savefile)] 10 | enum SomeEnum { 11 | Variant1(usize), 12 | Variant2{s:isize}, 13 | Variant3 14 | } 15 | 16 | #[derive(Savefile)] 17 | struct Simple2(usize,u8); 18 | #[derive(Savefile)] 19 | struct MyUnit(); 20 | #[derive(Savefile)] 21 | struct MySimple { 22 | integer: i8, 23 | theenum : Option, 24 | strings: Vec, 25 | simple2: Simple2, 26 | myunit: MyUnit, 27 | } 28 | 29 | fuzz_target!(|data: &[u8]| { 30 | let mut data = data; 31 | let _ = load_noschema::(&mut data,0); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | #git_release_enable = false 3 | changelog_update = false 4 | 5 | [[package]] 6 | name= "savefile" 7 | publish = true 8 | release = true 9 | 10 | 11 | [[package]] 12 | name= "savefile-derive" 13 | publish = true 14 | release = true 15 | 16 | 17 | [[package]] 18 | name= "savefile-abi" 19 | publish = true 20 | release = true 21 | 22 | [[package]] 23 | name= "savefile-test" 24 | publish = false 25 | release = false 26 | 27 | [[package]] 28 | name= "savefile-min-build" 29 | publish = false 30 | release = false 31 | 32 | [[package]] 33 | name= "savefile-abi-min" 34 | publish = false 35 | release = false 36 | 37 | [[package]] 38 | name= "savefile-abi-min-lib" 39 | publish = false 40 | release = false 41 | 42 | [[package]] 43 | name= "savefile-abi-min-lib-impl" 44 | publish = false 45 | release = false 46 | 47 | 48 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/bad_export2.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | use savefile_derive::savefile_abi_export; 12 | 13 | #[savefile_abi_exportable(version = 0)] 14 | pub trait ExampleTrait { 15 | fn get(&mut self, x: u32) -> u32; 16 | } 17 | #[derive(Default)] 18 | struct ExampleImpl { 19 | 20 | } 21 | 22 | // Forgot to implement trait 23 | savefile_abi_export!(ExampleImpl, ExampleTrait); 24 | //~^ 23:22: 23:47: the trait bound `ExampleImpl: ExampleTrait` is not satisfied [E0277] 25 | 26 | fn main() {} -------------------------------------------------------------------------------- /compile_tests/tests/tests.rs: -------------------------------------------------------------------------------- 1 | extern crate compiletest_rs as compiletest; 2 | 3 | use std::path::PathBuf; 4 | 5 | fn run_mode(mode: &'static str, custom_dir: Option<&'static str>) { 6 | let mut config = compiletest::Config::default().tempdir(); 7 | let cfg_mode = mode.parse().expect("Invalid mode"); 8 | 9 | config.mode = cfg_mode; 10 | 11 | let dir = custom_dir.unwrap_or(mode); 12 | config.src_base = PathBuf::from(format!("tests/{}", dir)); 13 | config.target_rustcflags = Some("-L target/debug -L target/debug/deps --edition 2021".to_string()); 14 | config.llvm_filecheck = Some("FileCheck".to_string().into()); 15 | 16 | config.strict_headers = true; 17 | 18 | compiletest::run_tests(&config); 19 | } 20 | 21 | #[test] 22 | fn compile_test() { 23 | run_mode("compile-fail", None); 24 | run_mode("run-pass", None); 25 | } 26 | -------------------------------------------------------------------------------- /savefile-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-derive" 3 | version.workspace = true 4 | authors = ["Anders Musikka "] 5 | repository = "https://github.com/avl/savefile" 6 | rust-version = "1.74" 7 | description = "Custom derive macros for savefile crate - simple, convenient, fast, versioned, binary serialization/deserialization library." 8 | 9 | readme = "../README.md" 10 | 11 | keywords = ["serialization", "deserialization"] 12 | 13 | categories = ["encoding"] 14 | 15 | license = "MIT/Apache-2.0" 16 | 17 | edition = "2021" 18 | 19 | [features] 20 | default = [] 21 | nightly = [] 22 | 23 | [lib] 24 | proc-macro = true 25 | 26 | [dependencies] 27 | quote = "1.0" 28 | syn = { version = "2.0.106" , features = ["full","extra-traits"]} 29 | proc-macro2 = { version = "1.0", features = ["nightly"] } 30 | proc-macro-error2 = "2.0.1" 31 | 32 | -------------------------------------------------------------------------------- /savefile-test/src/test_arrayvec.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_roundtrip; 2 | use arrayvec::ArrayVec; 3 | 4 | #[test] 5 | pub fn test_arrayvec0() { 6 | let a = ArrayVec::<(), 1>::new(); 7 | assert_roundtrip(a); 8 | } 9 | 10 | #[test] 11 | pub fn test_arrayvec1() { 12 | let mut a = ArrayVec::::new(); 13 | a.push(43i32); 14 | assert_roundtrip(a); 15 | } 16 | #[test] 17 | pub fn test_arrayvec2() { 18 | let mut a = ArrayVec::::new(); 19 | for _ in 0..100 { 20 | a.push(43i32); 21 | } 22 | assert_roundtrip(a); 23 | } 24 | #[test] 25 | pub fn test_arrayvec3() { 26 | let mut a = ArrayVec::<_, 128>::new(); 27 | for _ in 0..64 { 28 | a.push("Hello guys".to_string()); 29 | a.push("Hello again".to_string()); 30 | } 31 | assert_roundtrip(a); 32 | } 33 | #[test] 34 | pub fn test_arrayvec4() { 35 | let a = ArrayVec::::new(); 36 | assert_roundtrip(a); 37 | } 38 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/bad_export3.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | use savefile_derive::savefile_abi_export; 12 | 13 | #[savefile_abi_exportable(version = 0)] 14 | pub trait ExampleTrait { 15 | fn get(&mut self, x: u32) -> u32; 16 | } 17 | struct ExampleImpl { 18 | 19 | } 20 | 21 | impl ExampleTrait for ExampleImpl { 22 | fn get(&mut self, x: u32) -> u32 { 23 | x 24 | } 25 | } 26 | 27 | // Forgot to implement Default 28 | savefile_abi_export!(ExampleImpl, ExampleTrait); 29 | //~^ 28:1: 28:48: the trait bound `ExampleImpl: Default` is not satisfied [E0277] 30 | 31 | fn main() {} -------------------------------------------------------------------------------- /savefile-abi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-abi" 3 | version.workspace = true 4 | edition = "2021" 5 | authors = ["Anders Musikka "] 6 | documentation = "https://docs.rs/savefile-abi/" 7 | homepage = "https://github.com/avl/savefile/blob/master/savefile-abi/README.md" 8 | repository = "https://github.com/avl/savefile" 9 | rust-version = "1.74" 10 | 11 | description = "Easy to use, simple, stable ABI for Rust-libraries. Allows creating dynamically loadable plugins written in rust." 12 | 13 | readme = "README.md" 14 | 15 | keywords = ["dylib", "dlopen", "ffi"] 16 | 17 | license = "MIT/Apache-2.0" 18 | 19 | [dependencies] 20 | savefile = { path="../savefile", version = "=0.20.2" } 21 | savefile-derive = { path="../savefile-derive", version = "=0.20.2" } 22 | byteorder = "1.4" 23 | libloading = "0.8" 24 | bytes = {version = "1.8", optional = true } 25 | 26 | [features] 27 | default = ["bytes"] 28 | 29 | [dev-dependencies] 30 | async-trait = "0.1" 31 | 32 | [build-dependencies] 33 | rustc_version="0.4" 34 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/bad_export.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | use savefile_derive::savefile_abi_export; 12 | 13 | #[savefile_abi_exportable(version = 0)] 14 | pub trait ExampleTrait { 15 | fn get(&mut self, x: u32) -> u32; 16 | } 17 | #[derive(Default)] 18 | struct ExampleImpl { 19 | 20 | } 21 | impl ExampleTrait for ExampleImpl { 22 | fn get(&mut self, x: u32) -> u32 { 23 | x 24 | } 25 | } 26 | // Test what happens when you mix up the ordering of trait and impl: 27 | savefile_abi_export!(ExampleTrait, ExampleImpl); 28 | //~^ 27:22: 27:34: expected a type, found a trait [E0782] 29 | //~^^ 27:36: 27:47: expected trait, found struct `ExampleImpl` [E0404] 30 | 31 | fn main() {} -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018-2025 Anders Musikka 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /savefile-abi-min-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use savefile_derive::savefile_abi_exportable; 2 | use savefile_derive::Savefile; 3 | use std::fmt::{Debug, Formatter}; 4 | //use async_trait::async_trait; 5 | 6 | #[derive(Savefile)] 7 | pub struct MyStuff { 8 | pub x: u64, 9 | pub y: [u64; 10_000], 10 | } 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait AdderCallback { 14 | fn set(&self, value: u32); 15 | fn get(&self) -> u32; 16 | } 17 | 18 | #[savefile_abi_exportable(version = 0)] 19 | pub trait AdderInterface { 20 | fn add(&self, x: u32, y: &u32, z: &MyStuff) -> u32; 21 | fn sub(&self, x: u32, y: u32, cb: Box) -> u32; 22 | fn add_simple(&self, x: u32, y: u32) -> u32; 23 | fn do_nothing(&self); 24 | } 25 | impl Debug for dyn AdderInterface { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 27 | write!(f, "AdderInterface") 28 | } 29 | } 30 | /* 31 | 32 | #[async_trait] 33 | #[savefile_abi_exportable(version = 0)] 34 | pub trait AdderCallback { 35 | async fn set(&self, value: u32, big: u128, big2:u128); 36 | 37 | } 38 | */ 39 | -------------------------------------------------------------------------------- /compile_tests/tests/compile-fail/cow_smuggler.rs: -------------------------------------------------------------------------------- 1 | extern crate savefile; 2 | extern crate savefile_abi; 3 | extern crate savefile_derive; 4 | use std::collections::HashMap; 5 | use savefile::prelude::*; 6 | use savefile::{Deserialize, Deserializer, Serialize, Serializer}; 7 | use std::fmt::Debug; 8 | use std::io::{BufWriter, Cursor, Write}; 9 | use savefile_abi::AbiConnection; 10 | use savefile_derive::savefile_abi_exportable; 11 | use std::borrow::Cow; 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait CowSmuggler { 14 | fn smuggle(&mut self, x: Cow) -> Cow<'_, str>; 15 | } 16 | impl CowSmuggler for () { 17 | fn smuggle(&mut self, x: Cow) -> Cow<'_, str> { 18 | x 19 | //~^ 18:9: 18:10: lifetime may not live long enough 20 | } 21 | // If someone calls smuggle(..) with a reference to a long-lived, but not static item, 22 | // it is important to understand that the returned Cow cannot have the same lifetime. 23 | // it may have to be deserialized, and will then be an owned value. It will not be a reference 24 | // with the same lifetime as the argument. 25 | } 26 | fn main() {} -------------------------------------------------------------------------------- /savefile-abi-min-lib-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | use savefile_abi_min_lib::{AdderCallback, AdderInterface, MyStuff}; 2 | use savefile_derive::savefile_abi_export; 3 | 4 | pub struct AdderImplementation { 5 | _name: String, 6 | } 7 | 8 | impl Drop for AdderImplementation { 9 | fn drop(&mut self) { 10 | println!("Adder being dropped"); 11 | } 12 | } 13 | 14 | impl Default for AdderImplementation { 15 | fn default() -> Self { 16 | AdderImplementation { 17 | _name: "Adderaren Kalle".to_string(), 18 | } 19 | } 20 | } 21 | 22 | impl AdderInterface for AdderImplementation { 23 | fn add(&self, x: u32, y: &u32, z: &MyStuff) -> u32 { 24 | x + y + (z.x as u32) 25 | } 26 | 27 | fn sub(&self, x: u32, y: u32, cb: Box) -> u32 { 28 | let ret = x.saturating_sub(y); 29 | cb.set(ret); 30 | println!("----AFTER cb returned----"); 31 | ret 32 | } 33 | 34 | fn add_simple(&self, x: u32, y: u32) -> u32 { 35 | x + y 36 | } 37 | 38 | fn do_nothing(&self) {} 39 | } 40 | savefile_abi_export!(AdderImplementation, AdderInterface); 41 | -------------------------------------------------------------------------------- /savefile-test/src/test_recursive_types.rs: -------------------------------------------------------------------------------- 1 | use crate::roundtrip; 2 | use crate::savefile::WithSchema; 3 | use insta::assert_debug_snapshot; 4 | use savefile::WithSchemaContext; 5 | 6 | #[derive(Savefile, Debug)] 7 | struct Relay { 8 | relay: Box, 9 | } 10 | 11 | #[derive(Savefile, Debug)] 12 | struct RecursiveType { 13 | left: Option>, 14 | right: Option>, 15 | mid: Vec, 16 | } 17 | 18 | #[test] 19 | #[cfg(not(miri))] 20 | fn get_recursive_schema() { 21 | let mut temp = WithSchemaContext::new(); 22 | let schema = RecursiveType::schema(0, &mut temp); 23 | println!("Schema: {:#?}", schema); 24 | assert_debug_snapshot!(schema); 25 | } 26 | 27 | #[test] 28 | fn roundtrip_recursive_type() { 29 | let value = RecursiveType { 30 | left: Some(Box::new(RecursiveType { 31 | left: None, 32 | right: None, 33 | mid: vec![], 34 | })), 35 | right: None, 36 | mid: vec![Relay { 37 | relay: Box::new(RecursiveType { 38 | left: None, 39 | right: None, 40 | mid: vec![], 41 | }), 42 | }], 43 | }; 44 | roundtrip(value); 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "37 8 * * 1,5" 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Install nightly 21 | run: rustup toolchain install nightly && rustup toolchain install stable && rustup toolchain install 1.80 22 | - name: Miri (nightly) 23 | run: rustup component add --toolchain nightly miri && cd savefile-test && cargo +nightly miri test 24 | - name: Build (nightly) 25 | run: cargo +nightly build --workspace 26 | - name: Run tests (nightly) 27 | run: cargo +nightly test --workspace 28 | - name: Build min-deps (nightly) 29 | run: cargo +nightly build -p savefile-min-build 30 | - name: Build (stable) 31 | run: cargo +stable build --workspace 32 | - name: Run tests (stable) 33 | run: cargo +stable test --workspace 34 | - name: Build min-deps (stable) 35 | run: cargo +stable build -p savefile-min-build 36 | - name: compile_tests (stable) 37 | run: cd compile_tests && cargo +stable test 38 | - name: Build (1.80) 39 | run: cargo +1.80 build --workspace 40 | 41 | -------------------------------------------------------------------------------- /savefile-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile-test" 3 | version.workspace = true 4 | authors = ["Anders Musikka "] 5 | resolver = "2" 6 | edition = "2021" 7 | 8 | [features] 9 | default = ["external_benchmarks"] 10 | # Enable this to reduce risk of crashing on corrupt input. Provides sanity checks for sizes of objects. 11 | # This is mostly to be able to run fuzzers against the deserializers without them being guaranteed to easily find out-of-memory crashes. 12 | external_benchmarks = [] 13 | nightly=["savefile/nightly"] 14 | 15 | [dependencies] 16 | savefile = { path = "../savefile", features = ["size_sanity_checks", "encryption", "compression","bit-set","bit-vec","rustc-hash","serde_derive", "quickcheck", "nalgebra"]} 17 | savefile-derive = { path = "../savefile-derive", version = "=0.20.2" } 18 | savefile-abi = { path = "../savefile-abi" , features = ["bytes"]} 19 | bit-vec = "0.8" 20 | arrayvec="0.7" 21 | smallvec="*" 22 | indexmap = { version = "2.6"} 23 | byteorder="*" 24 | rand="0.8" 25 | parking_lot="0.12" 26 | serde="*" 27 | serde_derive="*" 28 | bincode="1.2.1" 29 | bit-set="0.8" 30 | rustc-hash="2.1.0" 31 | quickcheck="1.0" 32 | quickcheck_macros ="1.0" 33 | insta = { version = "1.41.1", features = ["yaml"] } 34 | nalgebra="0.33" 35 | tokio= { version = "1.47.1", features = ["test-util", "rt-multi-thread", "full"] } 36 | async-trait = "0.1" 37 | bytes="1.8" 38 | chrono="*" 39 | 40 | [build-dependencies] 41 | rustc_version="0.4" 42 | 43 | -------------------------------------------------------------------------------- /savefile/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use { 2 | super::deserialize_slice_as_vec, super::get_result_schema, super::get_schema, super::introspect_item, super::load, 3 | super::load_file, super::load_file_noschema, super::load_from_mem, super::load_noschema, super::save, 4 | super::save_file, super::save_file_noschema, super::save_noschema, super::save_to_mem, super::AbiRemoved, 5 | super::Canary1, super::Deserialize, super::Deserializer, super::Field, super::Introspect, super::IntrospectItem, 6 | super::IntrospectedElementKey, super::IntrospectionResult, super::Introspector, super::IntrospectorNavCommand, 7 | super::IsPacked, super::Packed, super::Removed, super::SavefileError, super::Schema, super::SchemaEnum, 8 | super::SchemaPrimitive, super::SchemaStruct, super::Serialize, super::Serializer, super::Variant, 9 | super::WithSchema, super::WithSchemaContext, 10 | }; 11 | 12 | pub use byteorder::{LittleEndian, ReadBytesExt}; 13 | pub use memoffset::offset_of_tuple; 14 | pub use memoffset::span_of; 15 | pub use std::mem::offset_of; 16 | pub use { 17 | super::AbiMethod, super::AbiMethodArgument, super::AbiMethodInfo, super::AbiTraitDefinition, super::ReceiverType, 18 | }; 19 | 20 | #[cfg(feature = "ring")] 21 | pub use super::{load_encrypted_file, save_encrypted_file, CryptoReader, CryptoWriter}; 22 | 23 | #[cfg(feature = "derive")] 24 | pub use savefile_derive::Packed; 25 | #[cfg(feature = "derive")] 26 | pub use savefile_derive::Savefile; 27 | #[cfg(feature = "derive")] 28 | pub use savefile_derive::SavefileIntrospectOnly; 29 | #[cfg(feature = "derive")] 30 | pub use savefile_derive::SavefileNoIntrospect; 31 | -------------------------------------------------------------------------------- /savefile-test/src/test_nested_non_repr_c.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use crate::roundtrip; 3 | use savefile::prelude::*; 4 | 5 | #[derive(Clone, Copy, Debug, PartialEq, Savefile)] 6 | #[savefile_unsafe_and_fast] 7 | struct CorrectlyAligned { 8 | //Used as a known-good to compare to 9 | x: u32, 10 | y: u32, 11 | } 12 | 13 | #[derive(Clone, Copy, Debug, PartialEq, Savefile)] 14 | //#[savefile_unsafe_and_fast] This now causes compilation failure, so test doesn't really work 15 | struct Inner { 16 | misaligner: u8, 17 | x: u32, 18 | } 19 | 20 | #[test] 21 | #[cfg(debug_assertions)] //This test only works in debug builds 22 | fn test_misaligned1() { 23 | assert_eq!(unsafe { Inner::repr_c_optimization_safe(0).is_yes() }, false); 24 | assert_eq!(unsafe { CorrectlyAligned::repr_c_optimization_safe(0).is_yes() }, true); 25 | } 26 | 27 | #[test] 28 | fn roundtrip_correctly_aligned() { 29 | roundtrip(CorrectlyAligned { x: 1, y: 2 }); 30 | roundtrip(Inner { misaligner: 43, x: 42 }); 31 | } 32 | 33 | #[derive(Clone, Copy, Debug, PartialEq, Savefile)] 34 | //#[savefile_unsafe_and_fast] This now causes compilation failure, so test doesn't really work 35 | struct Inner2 { 36 | x: u32, 37 | misaligner: u8, 38 | } 39 | 40 | #[test] 41 | #[cfg(debug_assertions)] //This test only works in debug builds 42 | fn test_misaligned2() { 43 | assert_eq!(unsafe { Inner2::repr_c_optimization_safe(0).is_yes() }, false); 44 | assert_eq!(unsafe { CorrectlyAligned::repr_c_optimization_safe(0).is_yes() }, true); 45 | } 46 | 47 | #[test] 48 | fn test_roundtrip_inner2() { 49 | roundtrip(Inner2 { x: 47, misaligner: 48 }); 50 | } 51 | -------------------------------------------------------------------------------- /savefile-abi-min/src/main.rs: -------------------------------------------------------------------------------- 1 | use savefile_abi::AbiConnection; 2 | use savefile_abi_min_lib::{AdderCallback, AdderInterface, MyStuff}; 3 | use std::ops::Deref; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | struct MyCallback { 7 | value: Arc>, 8 | } 9 | impl AdderCallback for MyCallback { 10 | fn set(&self, value: u32) { 11 | *self.value.lock().unwrap() = value; 12 | } 13 | 14 | fn get(&self) -> u32 { 15 | *self.value.lock().unwrap() 16 | } 17 | } 18 | 19 | impl Drop for MyCallback { 20 | fn drop(&mut self) { 21 | println!("Dropping AdderCallback"); 22 | } 23 | } 24 | 25 | #[inline(never)] 26 | pub fn call_add(adder: &AbiConnection, a: u32, b: u32) -> u32 { 27 | adder.add_simple(a, b) 28 | } 29 | #[no_mangle] 30 | pub extern "C" fn call_do_nothing(adder: &AbiConnection) { 31 | adder.do_nothing(); 32 | } 33 | 34 | fn main() { 35 | let connection = AbiConnection::::load_shared_library( 36 | "../target/debug/libsavefile_abi_min_lib_impl.so", //Change this to the proper path on your machine 37 | ) 38 | .unwrap(); 39 | 40 | let res = connection.add(1, &2, &Box::new(MyStuff { x: 43, y: [0; 10000] })); 41 | assert_eq!(res, 1 + 2 + 43); 42 | println!("Result: {res}"); 43 | let my_cb = Box::new(MyCallback { 44 | value: Arc::new(Mutex::new(32)), 45 | }); 46 | 47 | let my_arc = my_cb.value.clone(); 48 | println!("Before .sub"); 49 | let res2 = connection.sub(4, 1, my_cb); 50 | assert_eq!(res2, 3); 51 | println!("Result2: {} {:?}", res2, my_arc.lock().unwrap().deref()); 52 | 53 | assert_eq!(call_add(&connection, 1, 2), 3); 54 | } 55 | -------------------------------------------------------------------------------- /savefile-test/src/test_bounds.rs: -------------------------------------------------------------------------------- 1 | use savefile::{load, save, Packed, Serialize}; 2 | use std::borrow::Cow; 3 | use std::io::Cursor; 4 | 5 | #[derive(Savefile, PartialEq, Eq, Clone)] 6 | struct MaybeSerializable { 7 | field: Vec, 8 | } 9 | 10 | impl MaybeSerializable { 11 | fn save(&self, buf: &mut Vec) { 12 | save(buf, 0, self).unwrap(); 13 | } 14 | } 15 | 16 | #[test] 17 | fn test_serialize_maybe_serializable() { 18 | let mut temp = Vec::new(); 19 | { 20 | let temp_val = 42u32; 21 | let example = MaybeSerializable { 22 | field: vec![Cow::Borrowed(&temp_val)], 23 | }; 24 | 25 | example.save(&mut temp) 26 | } 27 | 28 | let roundtripped: MaybeSerializable = load(&mut Cursor::new(temp), 0).unwrap(); 29 | 30 | assert_eq!(roundtripped.field, vec![42]); 31 | } 32 | 33 | #[test] 34 | fn test_serialize_non_static() { 35 | let mut temp = Vec::new(); 36 | { 37 | let x = 42u32; 38 | let non_static = Cow::Borrowed(&x); 39 | save(&mut temp, 0, &non_static).unwrap(); 40 | } 41 | 42 | let roundtripped: Cow<'static, u32> = load(&mut Cursor::new(temp), 0).unwrap(); 43 | 44 | assert_eq!(*roundtripped, 42u32); 45 | } 46 | #[test] 47 | fn test_serialize_non_static2() { 48 | let mut temp = Vec::new(); 49 | { 50 | let x = 42u32; 51 | let non_static = Cow::Borrowed(&x); 52 | save(&mut temp, 0, &non_static).unwrap(); 53 | } 54 | 55 | let roundtripped: Cow<'static, u32> = load(&mut Cursor::new(temp), 0).unwrap(); 56 | 57 | assert_eq!(*roundtripped, 42u32); 58 | } 59 | 60 | fn test_serialize_non_static_with_lifetime<'a>(x: &'a u32) { 61 | let mut temp = Vec::new(); 62 | { 63 | let non_static = Cow::<'a, u32>::Borrowed(x); 64 | save(&mut temp, 0, &non_static).unwrap(); 65 | } 66 | 67 | let roundtripped: Cow<'a, u32> = load(&mut Cursor::new(temp), 0).unwrap(); 68 | 69 | assert_eq!(*roundtripped, 43u32); 70 | } 71 | 72 | #[test] 73 | fn test_with_specific_lifetime() { 74 | let x = 43u32; 75 | test_serialize_non_static_with_lifetime(&x); 76 | } 77 | -------------------------------------------------------------------------------- /savefile-test/src/cycles.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_roundtrip; 2 | use savefile::{get_schema, Removed, WithSchema, WithSchemaContext}; 3 | 4 | #[derive(Savefile, Debug, PartialEq)] 5 | enum Tree { 6 | Leaf, 7 | Node(Box, Box), 8 | } 9 | 10 | #[test] 11 | pub fn test_cyclic() { 12 | let example = Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf)); 13 | assert_roundtrip(example); 14 | 15 | let example = Tree::Node( 16 | Box::new(Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf))), 17 | Box::new(Tree::Leaf), 18 | ); 19 | assert_roundtrip(example); 20 | } 21 | 22 | #[derive(Savefile, Debug, PartialEq)] 23 | struct TreeNode { 24 | tree: Box, 25 | } 26 | 27 | #[derive(Savefile, Debug, PartialEq)] 28 | enum Tree2 { 29 | Leaf(String), 30 | Node(TreeNode), 31 | } 32 | #[test] 33 | pub fn test_cyclic2() { 34 | let example = Tree2::Node(TreeNode { 35 | tree: Box::new(Tree2::Leaf("hej".into())), 36 | }); 37 | assert_roundtrip(example); 38 | } 39 | 40 | #[derive(Savefile, Debug, PartialEq)] 41 | struct Version1LevelC(Box); 42 | 43 | #[derive(Savefile, Debug, PartialEq)] 44 | struct Version1LevelB(Box); 45 | 46 | #[derive(Savefile, Debug, PartialEq)] 47 | struct Version1LevelA(Option>); 48 | 49 | #[derive(Savefile, Debug, PartialEq)] 50 | struct Version1Base(Option>); 51 | 52 | #[derive(Savefile, Debug, PartialEq)] 53 | struct Version2LevelC(Box); 54 | 55 | #[derive(Savefile, Debug, PartialEq)] 56 | struct Version2LevelB(Box); 57 | 58 | #[derive(Savefile, Debug, PartialEq)] 59 | struct Version2LevelA(Option>); 60 | #[derive(Savefile, Debug, PartialEq)] 61 | struct Version2Base(Option>); 62 | 63 | #[test] 64 | #[should_panic( 65 | expected = "Saved schema differs from in-memory schema for version 0. Error: At location [./Version1Base/0/?/Version1LevelA/0/?/Version1LevelB/0/Version1LevelC/0]: Application protocol uses recursion up 3 levels, but foreign format uses 2" 66 | )] 67 | fn cycles_vertest1() { 68 | use crate::assert_roundtrip_to_new_version; 69 | assert_roundtrip_to_new_version(Version1Base(None), 0, Version2Base(None), 1); 70 | } 71 | -------------------------------------------------------------------------------- /savefile-test/src/test_generic.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_roundtrip; 2 | use savefile::prelude::*; 3 | use std::fmt::Debug; 4 | use std::marker::{PhantomData, PhantomPinned}; 5 | 6 | #[derive(Savefile, Debug, PartialEq)] 7 | pub struct ExampleGeneric { 8 | pub x: T, 9 | } 10 | 11 | #[derive(Savefile, Debug, PartialEq)] 12 | pub struct ExampleGeneric2 { 13 | pub x: T, 14 | } 15 | 16 | #[derive(Savefile, Debug, PartialEq)] 17 | pub struct ExampleGeneric3 18 | where 19 | T: Serialize + Deserialize, 20 | { 21 | pub x: T, 22 | } 23 | #[derive(Savefile, Debug, PartialEq)] 24 | pub struct ExampleGeneric4 { 25 | phantom: PhantomData, 26 | } 27 | 28 | #[derive(Savefile, Debug, PartialEq)] 29 | pub enum ExampleGenericEnum { 30 | Value1, 31 | Value2(T), 32 | } 33 | 34 | #[test] 35 | pub fn test_generic_example_u32() { 36 | let a = ExampleGeneric { x: 42u32 }; 37 | assert_roundtrip(a); 38 | } 39 | 40 | #[test] 41 | pub fn test_generic_example_string() { 42 | let a = ExampleGeneric { x: "hej".to_string() }; 43 | assert_roundtrip(a); 44 | } 45 | #[test] 46 | pub fn test_generic_example2_string() { 47 | let a = ExampleGeneric2 { x: "hej".to_string() }; 48 | assert_roundtrip(a); 49 | } 50 | #[test] 51 | pub fn test_generic_example3_tuple() { 52 | let a = ExampleGeneric3 { 53 | x: ("hej".to_string(), 42u32), 54 | }; 55 | assert_roundtrip(a); 56 | } 57 | #[test] 58 | pub fn test_generic_example4_phantom() { 59 | let a: ExampleGeneric4 = ExampleGeneric4 { phantom: PhantomData }; 60 | assert_roundtrip(a); 61 | } 62 | 63 | #[test] 64 | pub fn test_generic_example_enum() { 65 | let a = ExampleGenericEnum::Value2(42u32); 66 | assert_roundtrip(a); 67 | } 68 | 69 | #[repr(u8)] 70 | #[derive(Savefile, Debug, PartialEq)] 71 | pub enum ExampleGenericEnum2 { 72 | Value1(T1), 73 | Value2(T1), 74 | } 75 | #[test] 76 | pub fn test_generic_example_enum2() { 77 | let a = ExampleGenericEnum::Value2(42u8); 78 | assert_roundtrip(a); 79 | assert!(unsafe { ExampleGenericEnum2::::repr_c_optimization_safe(0) }.is_yes()); 80 | assert!(unsafe { ExampleGenericEnum2::::repr_c_optimization_safe(0) }.is_false()); 81 | //Padding 82 | } 83 | -------------------------------------------------------------------------------- /savefile-test/src/test_nested_repr_c.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use savefile::prelude::*; 3 | 4 | #[derive(Clone, Copy, Debug, PartialEq, Savefile)] 5 | #[savefile_unsafe_and_fast] 6 | struct Inner { 7 | x: u32, 8 | } 9 | 10 | #[derive(Clone, Copy, Debug, PartialEq, Savefile)] 11 | struct Nested { 12 | misaligner: u8, 13 | inner: Inner, 14 | } 15 | 16 | #[allow(unused)] 17 | #[derive(Clone, Copy, Debug, PartialEq, Savefile)] 18 | #[savefile_unsafe_and_fast] 19 | #[repr(u8)] 20 | pub enum TestReprEnum { 21 | A, 22 | B, 23 | C, 24 | } 25 | 26 | #[test] 27 | fn test_not_raw_memcpy2() { 28 | use std::io::Cursor; 29 | let sample = vec![Nested { 30 | misaligner: 0, 31 | inner: Inner { x: 32 }, 32 | }]; 33 | 34 | let mut f = Cursor::new(Vec::new()); 35 | { 36 | Serializer::save_noschema(&mut f, 0, &sample).unwrap(); 37 | } 38 | 39 | let f_internal_size = f.get_ref().len(); 40 | 41 | let vec_overhead = 8; 42 | let version = 4; 43 | let savefile_header = 9; 44 | let savefile_lib_version = 2; 45 | let is_compressed = 1; 46 | let misaligner = 1; 47 | let inner = 4; 48 | assert_eq!( 49 | f_internal_size, 50 | version + vec_overhead + misaligner + inner + savefile_header + savefile_lib_version + is_compressed 51 | ); //3 bytes padding also because of Packed-optimization 52 | } 53 | 54 | #[derive(Savefile, Clone, Copy)] 55 | #[savefile_unsafe_and_fast] 56 | #[repr(C)] 57 | struct MyUnitStruct {} 58 | 59 | #[derive(Savefile, Clone, Copy)] 60 | #[savefile_unsafe_and_fast] 61 | #[repr(C)] 62 | struct UnnamedFieldsStruct(usize); 63 | 64 | #[test] 65 | fn test_various_types_for_reprc() { 66 | assert_eq!(unsafe { <() as Packed>::repr_c_optimization_safe(0).is_yes() }, true); 67 | assert_eq!(unsafe { ::repr_c_optimization_safe(0) }.is_yes(), true); 68 | 69 | assert_eq!(unsafe { ::repr_c_optimization_safe(0) }.is_yes(), true); 70 | assert_eq!( 71 | unsafe { UnnamedFieldsStruct::repr_c_optimization_safe(0) }.is_yes(), 72 | false 73 | ); //usize is 32 bit on 32 bit platforms. 74 | 75 | assert_eq!(unsafe { <(u32, u32)>::repr_c_optimization_safe(0) }.is_yes(), true); 76 | assert_eq!(unsafe { <(u32, u8)>::repr_c_optimization_safe(0) }.is_yes(), false); 77 | assert_eq!( 78 | unsafe { <(u32, u8, u8, u16)>::repr_c_optimization_safe(0) }.is_yes(), 79 | true 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /savefile-abi/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Savefile-abi! 2 | 3 | Full docs: https://docs.rs/savefile-abi/latest/ 4 | 5 | Savefile-abi is a crate that is primarily meant to help building binary plugins using Rust. It supports 6 | many data types from the standard library, as well as custom user types. 7 | 8 | It supports async methods, through use of the #[async_trait] attribute macro. 9 | 10 | 11 | ```toml 12 | savefile-abi = "0.18" 13 | savefile = "0.18" 14 | savefile-derive = "0.18" 15 | ``` 16 | 17 | # Example 18 | 19 | Let's say we have a crate that defines this trait for adding u32s: 20 | 21 | *interface_crate* 22 | ```rust 23 | use savefile_derive::savefile_abi_exportable; 24 | 25 | #[savefile_abi_exportable(version=0)] 26 | pub trait AdderInterface { 27 | fn add(&self, x: u32, y: u32) -> u32; 28 | } 29 | 30 | ``` 31 | 32 | Now, we want to implement addition in a different crate, compile it to a shared library 33 | (.dll or .so), and use it in the first crate (or some other crate): 34 | 35 | *implementation_crate* 36 | ```rust 37 | use interface_crate::{AdderInterface}; 38 | use savefile_derive::savefile_abi_export; 39 | 40 | #[derive(Default)] 41 | struct MyAdder { } 42 | 43 | impl AdderInterface for MyAdder { 44 | fn add(&self, x: u32, y: u32) -> u32 { 45 | x + y 46 | } 47 | } 48 | 49 | // Export this implementation as the default-implementation for 50 | // the interface 'AdderInterface', for the current library. 51 | savefile_abi_export!(MyAdder, AdderInterface); 52 | 53 | ``` 54 | 55 | We add the following to Cargo.toml in our implementation crate: 56 | 57 | ```toml 58 | [lib] 59 | crate-type = ["cdylib"] 60 | ``` 61 | 62 | Now, in our application, we add a dependency to *interface_crate*, but not 63 | to *ImplementationCrate*. 64 | 65 | We then load the implementation dynamically at runtime: 66 | 67 | *app* 68 | 69 | ```rust 70 | use savefile_abi::{AbiConnection}; 71 | use interface_crate::{AdderInterface}; 72 | 73 | 74 | fn main() { 75 | // Load the implementation of `dyn AdderInterface` that was published 76 | // using the `savefile_abi_export!` above. 77 | let connection = AbiConnection:: 78 | ::load_shared_library("./ImplementationCrate.so").unwrap(); 79 | 80 | // The type `AbiConnection::` implements 81 | // the `AdderInterface`-trait, so we can use it to call its methods. 82 | assert_eq!(connection.add(1, 2), 3); 83 | } 84 | 85 | ``` 86 | 87 | # Limitations 88 | 89 | There are multiple limitations: 90 | 91 | * Tuples are presently not supported as direct function arguments! 92 | * There may be safety issues, Savefile-Abi is not mature yet. 93 | 94 | 95 | See full docs: https://docs.rs/savefile-abi/latest/ 96 | -------------------------------------------------------------------------------- /savefile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "savefile" 3 | version.workspace = true 4 | authors = ["Anders Musikka "] 5 | documentation = "https://docs.rs/savefile/" 6 | homepage = "https://github.com/avl/savefile/" 7 | repository = "https://github.com/avl/savefile" 8 | 9 | exclude = [ 10 | "*.bin" 11 | ] 12 | 13 | description = "Simple, convenient, fast, versioned, binary serialization/deserialization library." 14 | 15 | readme = "../README.md" 16 | 17 | keywords = ["serialization", "deserialization", "introspection"] 18 | 19 | categories = ["encoding"] 20 | 21 | license = "MIT/Apache-2.0" 22 | 23 | edition = "2021" 24 | 25 | rust-version = "1.80" 26 | 27 | [features] 28 | default = ["indexmap", "arrayvec", "smallvec", "bit-vec", "parking_lot","bit-set", "bit-set08", "bit-vec08", "chrono", "derive"] 29 | bit-set = ["dep:bit-set", "bit-vec"] 30 | bit-set08 = ["dep:bit-set08", "bit-vec08"] 31 | serde_derive = ["dep:serde_derive", "serde"] 32 | 33 | # Enable this to reduce risk of crashing on corrupt input. Provides sanity checks for sizes of objects. 34 | # This is mostly to be able to run fuzzers against the deserializers without them being guaranteed to easily find out-of-memory crashes. 35 | size_sanity_checks = [] 36 | # Use features only available on the nightly rust-compiler. 37 | # Enabling this provides slightly better introspection support. 38 | # Automatically set by build.rs for nightly compilers 39 | nightly=[] 40 | 41 | 42 | compression = ["bzip2"] 43 | 44 | encryption = ["ring", "rand"] 45 | 46 | derive = ["dep:savefile-derive"] 47 | 48 | [dependencies] 49 | bit-vec = { version = "0.6", optional = true} 50 | nalgebra = { version = "0.33", optional = true} 51 | bit-vec08 = { package="bit-vec", version = "0.8", optional = true} 52 | arrayvec = { version = "0.7", optional = true} 53 | smallvec = { version = "1.11", optional = true} 54 | indexmap = { version = "2.11.0", optional = true} 55 | chrono = { version = "0.4.39", optional = true} 56 | parking_lot = { version = "0.12", optional = true } 57 | ring = {version = "0.17.8", optional = true} 58 | rand = { version = "0.8", optional = true} 59 | bzip2 = {version = "0.4.4", optional = true} 60 | bit-set = {version = "0.5", optional = true} 61 | bit-set08 = {package="bit-set", version = "0.8", optional = true} 62 | rustc-hash = {version = "2.1.0", optional = true} 63 | memoffset = "0.9" 64 | byteorder = "1.4" 65 | savefile-derive = {path="../savefile-derive", version = "=0.20.2", optional = true } 66 | serde_derive = {version= "1.0", optional = true} 67 | serde = {version= "1.0", optional = true} 68 | quickcheck = {version= "1.0", optional = true} 69 | emath = {version = "0.30", optional = true} 70 | ecolor = {version = "0.30", optional = true} 71 | 72 | [dev-dependencies] 73 | savefile-derive = { path="../savefile-derive", version = "=0.20.2" } 74 | 75 | [build-dependencies] 76 | rustc_version="0.4" 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/snapshots/savefile_test__savefile_abi_test__argument_backward_compatibility__abi_schemas_get_def.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs 3 | expression: exportable 4 | --- 5 | name: ArgInterfaceV2 6 | methods: 7 | - name: sums 8 | info: 9 | return_value: 10 | Primitive: schema_u32 11 | receiver: Shared 12 | arguments: 13 | - schema: 14 | Struct: 15 | dbg_name: ArgArgument 16 | size: 8 17 | alignment: 4 18 | fields: 19 | - name: data1 20 | value: 21 | Primitive: schema_u32 22 | offset: ~ 23 | - name: data2 24 | value: 25 | Primitive: schema_u32 26 | offset: 0 27 | - schema: 28 | Struct: 29 | dbg_name: ArgArgument 30 | size: 8 31 | alignment: 4 32 | fields: 33 | - name: data1 34 | value: 35 | Primitive: schema_u32 36 | offset: ~ 37 | - name: data2 38 | value: 39 | Primitive: schema_u32 40 | offset: 0 41 | async_trait_heuristic: false 42 | - name: enum_arg 43 | info: 44 | return_value: 45 | Primitive: 46 | schema_string: CapacityDataLength 47 | receiver: Shared 48 | arguments: 49 | - schema: 50 | Enum: 51 | dbg_name: EnumArgument 52 | variants: 53 | - name: Variant1 54 | discriminant: 0 55 | fields: [] 56 | - name: Variant2 57 | discriminant: 1 58 | fields: [] 59 | discriminant_size: 1 60 | has_explicit_repr: false 61 | size: 1 62 | alignment: 1 63 | async_trait_heuristic: false 64 | - name: function_existing_in_v2 65 | info: 66 | return_value: ZeroSize 67 | receiver: Shared 68 | arguments: [] 69 | async_trait_heuristic: false 70 | - name: closure_test 71 | info: 72 | return_value: ZeroSize 73 | receiver: Shared 74 | arguments: 75 | - schema: 76 | Boxed: 77 | FnClosure: 78 | - false 79 | - name: __1_owning_ 80 | methods: 81 | - name: docall 82 | info: 83 | return_value: ZeroSize 84 | receiver: Shared 85 | arguments: [] 86 | async_trait_heuristic: false 87 | sync: false 88 | send: false 89 | async_trait_heuristic: false 90 | sync: false 91 | send: false 92 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/enum_tests.rs: -------------------------------------------------------------------------------- 1 | use crate::savefile_abi_test::basic_abi_tests::{TestInterface, TestInterfaceImpl}; 2 | use savefile_abi::{AbiConnection, AbiExportable}; 3 | 4 | #[derive(Savefile, Clone)] 5 | #[repr(C, u8)] 6 | pub enum AbiSimpleEnum { 7 | Variant1, 8 | Variant2(u32, String, String, String), 9 | Variant3(Vec, Vec<()>), 10 | } 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait SimpleInterfaceWithEnums { 14 | fn count_arg(&self, x: &AbiSimpleEnum) -> u32; 15 | fn count_arg_owned(&self, x: AbiSimpleEnum) -> u32; 16 | 17 | fn closure_arg(&self, x: &dyn Fn(&AbiSimpleEnum) -> AbiSimpleEnum) -> AbiSimpleEnum; 18 | } 19 | 20 | struct Implementation {} 21 | 22 | impl SimpleInterfaceWithEnums for Implementation { 23 | fn count_arg(&self, x: &AbiSimpleEnum) -> u32 { 24 | match x { 25 | AbiSimpleEnum::Variant1 => 0, 26 | AbiSimpleEnum::Variant2(c, _, _, _) => *c, 27 | AbiSimpleEnum::Variant3(_, _) => 1, 28 | } 29 | } 30 | fn count_arg_owned(&self, x: AbiSimpleEnum) -> u32 { 31 | match x { 32 | AbiSimpleEnum::Variant1 => 0, 33 | AbiSimpleEnum::Variant2(c, _, _, _) => c, 34 | AbiSimpleEnum::Variant3(_, _) => 1, 35 | } 36 | } 37 | 38 | fn closure_arg(&self, x: &dyn Fn(&AbiSimpleEnum) -> AbiSimpleEnum) -> AbiSimpleEnum { 39 | x(&AbiSimpleEnum::Variant1) 40 | } 41 | } 42 | 43 | #[test] 44 | fn check_various_vec_layouts() { 45 | use savefile::calculate_vec_memory_layout; 46 | println!("{:?}", calculate_vec_memory_layout::()); 47 | println!("{:?}", calculate_vec_memory_layout::()); 48 | println!("{:?}", calculate_vec_memory_layout::()); 49 | println!("{:?}", calculate_vec_memory_layout::>()); 50 | println!("{:?}", calculate_vec_memory_layout::<&'static str>()); 51 | } 52 | 53 | #[test] 54 | fn test_simple_enum_owned() { 55 | let boxed: Box = Box::new(Implementation {}); 56 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 57 | assert_eq!( 58 | conn.count_arg_owned(AbiSimpleEnum::Variant2(42, "hej".into(), "då".into(), "osv".into())), 59 | 42 60 | ); 61 | } 62 | 63 | #[test] 64 | fn test_simple_enum_ref() { 65 | let boxed: Box = Box::new(Implementation {}); 66 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 67 | 68 | assert_eq!( 69 | conn.count_arg(&AbiSimpleEnum::Variant2(42, "hej".into(), "då".into(), "osv".into())), 70 | 42 71 | ); 72 | let zero: Vec<()> = vec![]; 73 | println!( 74 | "Mem: {:?}, zero ptr: {:?}", 75 | std::mem::size_of::>(), 76 | zero.as_ptr() 77 | ); 78 | assert_eq!(conn.count_arg(&AbiSimpleEnum::Variant3(vec![1, 2, 3], vec![])), 1); 79 | } 80 | 81 | #[test] 82 | fn test_closure_arg() { 83 | let boxed: Box = Box::new(Implementation {}); 84 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 85 | 86 | conn.closure_arg(&|x: &AbiSimpleEnum| x.clone()); 87 | } 88 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/test_different_trait_definitions.rs: -------------------------------------------------------------------------------- 1 | use savefile_abi::RawAbiCallResult::AbiError; 2 | use savefile_abi::{AbiConnection, AbiExportable}; 3 | 4 | #[savefile_abi_exportable(version = 0)] 5 | pub trait InterfaceV1 { 6 | fn old(&self); 7 | fn add(&self, x: u32, y: u32) -> u32; 8 | fn mul(&self, x: u32, y: u32) -> u32; 9 | } 10 | 11 | #[savefile_abi_exportable(version = 0)] 12 | pub trait InterfaceV2 { 13 | fn newer1(&self); 14 | fn newer2(&self); 15 | fn mul(&self, x: u32, y: u32) -> u32; 16 | fn add(&self, x: u32, y: u32) -> u32; 17 | } 18 | 19 | #[derive(Default)] 20 | struct Implementation1 {} 21 | 22 | #[derive(Default)] 23 | struct Implementation2 {} 24 | 25 | impl InterfaceV1 for Implementation1 { 26 | fn old(&self) {} 27 | 28 | fn add(&self, x: u32, y: u32) -> u32 { 29 | x + y 30 | } 31 | fn mul(&self, x: u32, y: u32) -> u32 { 32 | x * y 33 | } 34 | } 35 | savefile_abi_export!(Implementation1, InterfaceV1); 36 | impl InterfaceV2 for Implementation2 { 37 | fn newer1(&self) {} 38 | 39 | fn newer2(&self) {} 40 | 41 | fn mul(&self, x: u32, y: u32) -> u32 { 42 | x * y 43 | } 44 | fn add(&self, x: u32, y: u32) -> u32 { 45 | x + y 46 | } 47 | } 48 | savefile_abi_export!(Implementation2, InterfaceV2); 49 | 50 | #[test] 51 | pub fn test_caller_has_older_version() { 52 | let iface2: Box = Box::new(Implementation2 {}); 53 | let conn1 = unsafe { 54 | AbiConnection::::from_boxed_trait_for_test( 55 | ::ABI_ENTRY, 56 | iface2, 57 | ) 58 | } 59 | .unwrap(); 60 | 61 | assert_eq!(conn1.add(2, 3), 5); 62 | assert_eq!(conn1.mul(2, 3), 6); 63 | } 64 | 65 | #[test] 66 | pub fn test_caller_has_newer_version() { 67 | let iface1: Box = Box::new(Implementation1 {}); 68 | let conn1 = unsafe { 69 | AbiConnection::::from_boxed_trait_for_test( 70 | ::ABI_ENTRY, 71 | iface1, 72 | ) 73 | } 74 | .unwrap(); 75 | 76 | assert_eq!(conn1.add(2, 3), 5); 77 | assert_eq!(conn1.mul(2, 3), 6); 78 | } 79 | 80 | #[test] 81 | #[should_panic(expected = "Method 'old' does not exist in implementation.")] 82 | pub fn test_calling_removed_method() { 83 | let iface2: Box = Box::new(Implementation2 {}); 84 | let conn1 = unsafe { 85 | AbiConnection::::from_boxed_trait_for_test( 86 | ::ABI_ENTRY, 87 | iface2, 88 | ) 89 | } 90 | .unwrap(); 91 | 92 | conn1.old(); 93 | } 94 | 95 | #[test] 96 | #[should_panic(expected = "Method 'newer1' does not exist in implementation.")] 97 | pub fn test_calling_not_yet_existing_method() { 98 | let iface1: Box = Box::new(Implementation1 {}); 99 | let conn1 = unsafe { 100 | AbiConnection::::from_boxed_trait_for_test( 101 | ::ABI_ENTRY, 102 | iface1, 103 | ) 104 | } 105 | .unwrap(); 106 | 107 | conn1.newer1(); 108 | } 109 | -------------------------------------------------------------------------------- /savefile-test/src/test_more_async.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use bytes::BufMut; 3 | use savefile_abi::{AbiConnection, AbiWaker}; 4 | use std::future::Future; 5 | use std::hint::black_box; 6 | use std::pin::Pin; 7 | use std::sync::{Arc, Mutex}; 8 | use std::task::{Context, Poll, Waker}; 9 | use std::time::Duration; 10 | #[cfg(feature = "nightly")] 11 | use test::Bencher; 12 | use tokio::pin; 13 | 14 | #[async_trait] 15 | #[savefile_abi_exportable(version = 0)] 16 | pub trait SimpleAsyncInterface { 17 | async fn add_async(&mut self, x: u32, y: u32) -> u32; 18 | async fn add_async2(&self, x: u32, y: u32) -> u32; 19 | async fn internal_inc(&mut self, x: u32) -> u32; 20 | } 21 | 22 | #[savefile_abi_exportable(version = 0)] 23 | pub trait BoxedAsyncInterface { 24 | fn add_async(&mut self, x: u32, y: u32) -> Pin>>; 25 | } 26 | 27 | #[derive(Default)] 28 | struct SimpleImpl { 29 | internal: u32, 30 | } 31 | 32 | impl BoxedAsyncInterface for SimpleImpl { 33 | fn add_async(&mut self, x: u32, y: u32) -> Pin>> { 34 | Box::pin(async move { 35 | tokio::time::sleep(Duration::from_millis(1)).await; 36 | format!("{}", x + y) 37 | }) 38 | } 39 | } 40 | 41 | #[async_trait] 42 | impl SimpleAsyncInterface for SimpleImpl { 43 | async fn add_async(&mut self, x: u32, y: u32) -> u32 { 44 | tokio::time::sleep(Duration::from_millis(10)).await; 45 | x + y 46 | } 47 | 48 | async fn add_async2(&self, x: u32, y: u32) -> u32 { 49 | x + y 50 | } 51 | async fn internal_inc(&mut self, x: u32) -> u32 { 52 | tokio::time::sleep(Duration::from_millis(1)).await; 53 | self.internal += x; 54 | self.internal 55 | } 56 | } 57 | 58 | #[tokio::test] 59 | async fn abi_test_async() { 60 | let boxed: Box = Box::new(SimpleImpl::default()); 61 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 62 | let mut acc = 0; 63 | for i in 0..10 { 64 | assert_eq!(acc + i, conn.add_async2(acc, i).await); 65 | acc = conn.add_async(acc, i).await; 66 | } 67 | 68 | assert_eq!(acc, 45); 69 | assert_eq!(conn.internal_inc(10).await, 10); 70 | assert_eq!(conn.internal_inc(10).await, 20); 71 | } 72 | 73 | #[tokio::test] 74 | async fn abi_test_boxed_async() { 75 | let boxed: Box = Box::new(SimpleImpl::default()); 76 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 77 | 78 | assert_eq!(conn.add_async(1, 2).await, "3"); 79 | } 80 | 81 | #[cfg(feature = "nightly")] 82 | #[cfg(not(miri))] 83 | #[bench] 84 | fn bench_simple_async_call(b: &mut Bencher) { 85 | let boxed: Box = Box::new(SimpleImpl::default()); 86 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 87 | 88 | b.iter(|| { 89 | let waker = Waker::from(Arc::new(AbiWaker::new(Box::new(|| {})))); 90 | let mut context = Context::from_waker(&waker); 91 | let x = conn.add_async2(1, 2); 92 | pin!(x); 93 | match x.poll(&mut context) { 94 | Poll::Ready(sum) => black_box(sum), 95 | Poll::Pending => { 96 | unreachable!() 97 | } 98 | } 99 | }) 100 | } 101 | -------------------------------------------------------------------------------- /savefile-test/src/enum_variant_versioning.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_roundtrip_version; 2 | use crate::{assert_roundtrip, assert_roundtrip_to_new_version}; 3 | use savefile::{Packed, Removed}; 4 | 5 | #[repr(u8)] 6 | #[derive(Savefile, Debug, PartialEq)] 7 | pub enum EnumAVer1 { 8 | Var1, 9 | Var2, 10 | } 11 | 12 | #[repr(u16)] 13 | #[derive(Savefile, Debug, PartialEq)] 14 | pub enum EnumAVer2 { 15 | Var1, 16 | Var2, 17 | } 18 | #[repr(u32)] 19 | #[derive(Savefile, Debug, PartialEq)] 20 | pub enum EnumAVer3 { 21 | Var1, 22 | Var2, 23 | } 24 | 25 | #[test] 26 | #[should_panic( 27 | expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.EnumAVer1]: In memory enum has a representation with 2 bytes for the discriminant, but disk format has 1." 28 | )] 29 | fn test_change_of_discriminant_size() { 30 | assert_roundtrip_to_new_version(EnumAVer1::Var1, 0, EnumAVer2::Var1, 1); 31 | } 32 | #[test] 33 | #[should_panic( 34 | expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.EnumAVer2]: In memory enum has a representation with 1 bytes for the discriminant, but disk format has 2." 35 | )] 36 | fn test_change_of_discriminant_size2() { 37 | assert_roundtrip_to_new_version(EnumAVer2::Var1, 0, EnumAVer1::Var1, 1); 38 | } 39 | 40 | #[test] 41 | #[should_panic( 42 | expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.EnumAVer2]: In memory enum has a representation with 4 bytes for the discriminant, but disk format has 2." 43 | )] 44 | fn test_change_of_discriminant_size3() { 45 | assert_roundtrip_to_new_version(EnumAVer2::Var1, 0, EnumAVer3::Var1, 1); 46 | } 47 | #[test] 48 | #[should_panic( 49 | expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.EnumAVer3]: In memory enum has a representation with 2 bytes for the discriminant, but disk format has 4." 50 | )] 51 | fn test_change_of_discriminant_size4() { 52 | assert_roundtrip_to_new_version(EnumAVer3::Var1, 0, EnumAVer2::Var1, 1); 53 | } 54 | 55 | #[derive(Savefile, Debug, PartialEq)] 56 | pub enum EnumBVer1 { 57 | Var1, 58 | Var2, 59 | } 60 | 61 | #[derive(Savefile, Debug, PartialEq)] 62 | pub enum EnumBVer2 { 63 | Var1, 64 | Var2, 65 | #[savefile_versions = "1.."] 66 | Var3, 67 | } 68 | 69 | #[test] 70 | fn test_change_add_enum_variants() { 71 | assert_roundtrip_to_new_version(EnumBVer1::Var1, 0, EnumBVer2::Var1, 0); 72 | } 73 | 74 | #[derive(Savefile, Debug, PartialEq)] 75 | pub enum EnumBVer3 { 76 | Var1, 77 | Var2, 78 | #[savefile_versions = "1.."] 79 | Var3, 80 | #[savefile_versions = "2.."] 81 | Var4, 82 | } 83 | #[test] 84 | fn test_change_add_enum_variants2() { 85 | assert_roundtrip_to_new_version(EnumBVer2::Var3, 1, EnumBVer3::Var3, 1); 86 | } 87 | #[test] 88 | #[should_panic(expected = "Enum EnumBVer2, variant Var3 is not present in version 0")] 89 | fn test_change_add_enum_variants3() { 90 | assert_roundtrip_to_new_version(EnumBVer2::Var3, 0, EnumBVer3::Var3, 0); 91 | } 92 | 93 | #[derive(Savefile, Debug, PartialEq)] 94 | #[repr(u32)] 95 | pub enum EnumCVer2 { 96 | Var1, 97 | Var2 { 98 | #[savefile_versions = "..0"] 99 | a: Removed, 100 | b: u32, 101 | }, 102 | } 103 | #[test] 104 | fn test_change_remove_enum_field() { 105 | assert!(unsafe { EnumCVer2::repr_c_optimization_safe(0) }.is_false()); 106 | assert!(unsafe { EnumCVer2::repr_c_optimization_safe(1) }.is_yes()); 107 | assert_roundtrip_version( 108 | EnumCVer2::Var2 { 109 | b: 42, 110 | a: Removed::new(), 111 | }, 112 | 1, 113 | true, 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://github.com/avl/savefile/actions/workflows/rust.yml/badge.svg) 2 | 3 | **Having trouble with new version 0.20? - See upgrade guide further down in this document!** 4 | 5 | # Introduction to Savefile 6 | 7 | Savefile is a crate to effortlessly serialize rust structs and enums. It uses 8 | an efficient binary format. It can serialize to anything implementing the 9 | Write trait, and then deserialize from anything implementing the Read trait. This 10 | means that savefile can be used to easily save in-memory data structures to 11 | disk for persistent storage. 12 | 13 | Docs: https://docs.rs/savefile/latest/savefile/ 14 | 15 | # Capabilities 16 | 17 | * **Easy to use** Most std datatypes are supported, and the derive macro can be used for most user-types. 18 | * **Reliable** - Savefile has an extensive test suite. 19 | * **Backward compatible** - Savefile supports schema-versioning, with built-in verification and detailed error messages on schema mismatch. 20 | * **Fast** - Savefile can serialize/deserialize many data types quickly by *safely* treating them as raw bytes. 21 | * **Safe** - Savefile can be used without requiring any unsafe code from the user. 22 | 23 | 24 | # Savefile-Abi 25 | 26 | Savefile-Abi is a related crate, which allows publishing forward- and backward compatible 27 | shared libraries, written in rust, to be used as binary plugins in rust-programs. 28 | 29 | Docs: https://docs.rs/savefile-abi/latest/ 30 | 31 | 32 | # Usage 33 | Cargo.toml: 34 | ```toml 35 | savefile = "0.20" 36 | savefile-derive = "0.20" 37 | ``` 38 | 39 | main.rs: 40 | ```rust 41 | extern crate savefile; 42 | use savefile::prelude::*; 43 | 44 | #[macro_use] 45 | extern crate savefile_derive; 46 | 47 | 48 | #[derive(Savefile)] 49 | struct Player { 50 | name : String, 51 | strength : u32, 52 | inventory : Vec, 53 | } 54 | 55 | fn save_player(player:&Player) { 56 | save_file("save.bin", 0, player).unwrap(); 57 | } 58 | 59 | fn load_player() -> Player { 60 | load_file("save.bin", 0).unwrap() 61 | } 62 | 63 | fn main() { 64 | let player = Player { name: "Steve".to_string(), strength: 42, 65 | inventory: vec!( 66 | "wallet".to_string(), 67 | "car keys".to_string(), 68 | "glasses".to_string())}; 69 | 70 | save_player(&player); 71 | 72 | let reloaded_player = load_player(); 73 | 74 | assert_eq!(reloaded_player.name,"Steve".to_string()); 75 | } 76 | 77 | ``` 78 | 79 | # Docs 80 | 81 | The savefile docs are available at: https://docs.rs/savefile/latest/savefile/ 82 | 83 | # Changelog 84 | 85 | See the [changelog](https://github.com/avl/savefile/blob/master/CHANGELOG.md). 86 | 87 | # Features and goals 88 | 89 | Features savefile has: 90 | 91 | * Fast binary serialization and deserialization 92 | * Support for old versions of the save format 93 | * Completely automatic implementation using "custom derive". You do not have to 94 | figure out how your data is to be saved. 95 | 96 | Features savefile does not have: 97 | * Support for recursive data-structures 98 | 99 | Features savefile does not have, and will not have: 100 | 101 | * Support for external protocols/data formats. There'll never be json, yaml, 102 | xml or any other backends. Savefile uses the savefile format, period. 103 | * Support for serializing graphs. Savefile can serialize your data if it has a 104 | tree structure in RAM, _without_ loops. 105 | * Support for serializing boxed traits ("objects"). You can (probably) hack this in by manually 106 | implementing the Serialize and Deserialize traits and somehow select concrete types in 107 | the deserializer manually. 108 | 109 | # Upgrade Guide 110 | 111 | ## Upgrading from pre 0.16.x: 112 | 113 | ### "the trait bound `MyStuff: WithSchema` is not satisfied" 114 | This probably means you've forgotten to derive the Savefile-traits. Add a `#[derive(Savefile)]`. 115 | 116 | ### the trait `ReprC` is not implemented 117 | 118 | This one is easy. `ReprC` has been renamed to `Packed`. Just change to `Packed` and things should work. 119 | 120 | # License 121 | 122 | Savefile is licensed under either of 123 | 124 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 125 | http://www.apache.org/licenses/LICENSE-2.0) 126 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 127 | http://opensource.org/licenses/MIT) 128 | 129 | at your option. 130 | 131 | MIT License text: 132 | 133 | ``` 134 | Copyright 2018-2025 Anders Musikka 135 | 136 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 137 | 138 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 139 | 140 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 141 | 142 | ``` 143 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/closure_tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | use crate::savefile_abi_test::basic_abi_tests::{CallbackImpl, TestInterface, TestInterfaceImpl}; 3 | use crate::savefile_abi_test::closure_tests::new_version::ExampleImplementationNewer; 4 | use savefile_abi::{AbiConnection, AbiExportable}; 5 | 6 | #[derive(Savefile)] 7 | pub struct CustomArg { 8 | pub x: u32, 9 | } 10 | 11 | #[savefile_abi_exportable(version = 0)] 12 | pub trait Example { 13 | fn call_mut_closure(&self, simple: &mut dyn FnMut(u32, &u32) -> u32); 14 | fn call_closure(&self, simple: &dyn Fn(u32, &u32) -> u32); 15 | fn call_closure_with_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> u32) -> u32; 16 | 17 | fn call_closure_return_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> CustomArg) -> u32; 18 | } 19 | 20 | pub mod new_version { 21 | 22 | #[derive(Savefile)] 23 | pub struct CustomArg { 24 | pub x: u32, 25 | #[savefile_versions = "1.."] 26 | pub y: String, 27 | } 28 | #[savefile_abi_exportable(version = 1)] 29 | pub trait Example { 30 | fn call_mut_closure(&self, simple: &mut dyn FnMut(u32, &u32) -> u32); 31 | fn call_closure(&self, simple: &dyn Fn(u32, &u32) -> u32); 32 | fn call_closure_with_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> u32) -> u32; 33 | fn call_closure_return_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> CustomArg) -> u32; 34 | } 35 | pub struct ExampleImplementationNewer {} 36 | impl Example for ExampleImplementationNewer { 37 | fn call_mut_closure(&self, _simple: &mut dyn FnMut(u32, &u32) -> u32) { 38 | todo!() 39 | } 40 | 41 | fn call_closure(&self, _simple: &dyn Fn(u32, &u32) -> u32) { 42 | todo!() 43 | } 44 | 45 | fn call_closure_with_custom_arg(&self, _simple: &dyn Fn(&CustomArg) -> u32) -> u32 { 46 | todo!() 47 | } 48 | 49 | fn call_closure_return_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> CustomArg) -> u32 { 50 | simple(&CustomArg { 51 | x: 42, 52 | y: "hello".to_string(), 53 | }) 54 | .x 55 | } 56 | } 57 | } 58 | 59 | struct ExampleImplementation {} 60 | impl Example for ExampleImplementation { 61 | fn call_mut_closure(&self, simple: &mut dyn FnMut(u32, &u32) -> u32) { 62 | println!("Output: {}", simple(43, &42)); 63 | } 64 | fn call_closure(&self, simple: &dyn Fn(u32, &u32) -> u32) { 65 | println!("Output: {}", simple(43, &42)); 66 | } 67 | 68 | fn call_closure_with_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> u32) -> u32 { 69 | let t = simple(&CustomArg { x: 42 }); 70 | println!("Output: {}", t); 71 | t 72 | } 73 | 74 | fn call_closure_return_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> CustomArg) -> u32 { 75 | let x = simple(&CustomArg { x: 42 }); 76 | x.x 77 | } 78 | } 79 | 80 | #[test] 81 | fn test_closure() { 82 | let boxed: Box = Box::new(ExampleImplementation {}); 83 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 84 | 85 | conn.call_closure(&|x, y| x + y); 86 | } 87 | 88 | #[test] 89 | fn test_mut_closure() { 90 | let boxed: Box = Box::new(ExampleImplementation {}); 91 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 92 | 93 | let mut num_calls = 0; 94 | conn.call_mut_closure(&mut |x, y| { 95 | num_calls += 1; 96 | 97 | x + y 98 | }); 99 | assert_eq!(num_calls, 1); 100 | } 101 | 102 | #[test] 103 | fn test_closure_with_custom_arg() { 104 | let boxed: Box = Box::new(ExampleImplementation {}); 105 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 106 | 107 | let result = conn.call_closure_with_custom_arg(&|arg| arg.x); 108 | assert_eq!(result, 42); 109 | } 110 | 111 | #[test] 112 | fn test_closure_with_custom_arg_call_older() { 113 | let iface1: Box = Box::new(ExampleImplementation {}); 114 | let conn = 115 | unsafe { AbiConnection::::from_boxed_trait_for_test(::ABI_ENTRY, iface1) }.unwrap(); 116 | 117 | let result = conn.call_closure_with_custom_arg(&|arg| arg.x); 118 | assert_eq!(result, 42); 119 | } 120 | #[test] 121 | fn test_closure_with_custom_return_call_older() { 122 | let iface1: Box = Box::new(ExampleImplementation {}); 123 | let conn = unsafe { 124 | AbiConnection::::from_boxed_trait_for_test(::ABI_ENTRY, iface1) 125 | } 126 | .unwrap(); 127 | 128 | //let old_def = ::get_definition(0); 129 | //println!("Old def: {:#?}", old_def); 130 | { 131 | use crate::savefile_abi_test::closure_tests::new_version::Example; 132 | let result = conn.call_closure_return_custom_arg(&|arg| new_version::CustomArg { 133 | x: arg.x, 134 | y: "hej".to_string(), 135 | }); 136 | 137 | assert_eq!(result, 42); 138 | } 139 | 140 | _ = conn; 141 | } 142 | #[test] 143 | fn test_closure_with_custom_return_call_newer() { 144 | let iface1: Box = Box::new(ExampleImplementationNewer {}); 145 | let conn = unsafe { 146 | AbiConnection::::from_boxed_trait_for_test(::ABI_ENTRY, iface1) 147 | } 148 | .unwrap(); 149 | 150 | let result = conn.call_closure_return_custom_arg(&|arg| CustomArg { x: arg.x }); 151 | assert_eq!(result, 42); 152 | 153 | _ = conn; 154 | } 155 | -------------------------------------------------------------------------------- /savefile-test/src/test_enum_many_variants.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_roundtrip; 2 | 3 | #[repr(u16)] 4 | #[derive(Savefile, Debug, PartialEq)] 5 | enum ManyVariants { 6 | Variant0, 7 | Variant1, 8 | Variant2, 9 | Variant3, 10 | Variant4, 11 | Variant5, 12 | Variant6, 13 | Variant7, 14 | Variant8, 15 | Variant9, 16 | Variant10, 17 | Variant11, 18 | Variant12, 19 | Variant13, 20 | Variant14, 21 | Variant15, 22 | Variant16, 23 | Variant17, 24 | Variant18, 25 | Variant19, 26 | Variant20, 27 | Variant21, 28 | Variant22, 29 | Variant23, 30 | Variant24, 31 | Variant25, 32 | Variant26, 33 | Variant27, 34 | Variant28, 35 | Variant29, 36 | Variant30, 37 | Variant31, 38 | Variant32, 39 | Variant33, 40 | Variant34, 41 | Variant35, 42 | Variant36, 43 | Variant37, 44 | Variant38, 45 | Variant39, 46 | Variant40, 47 | Variant41, 48 | Variant42, 49 | Variant43, 50 | Variant44, 51 | Variant45, 52 | Variant46, 53 | Variant47, 54 | Variant48, 55 | Variant49, 56 | Variant50, 57 | Variant51, 58 | Variant52, 59 | Variant53, 60 | Variant54, 61 | Variant55, 62 | Variant56, 63 | Variant57, 64 | Variant58, 65 | Variant59, 66 | Variant60, 67 | Variant61, 68 | Variant62, 69 | Variant63, 70 | Variant64, 71 | Variant65, 72 | Variant66, 73 | Variant67, 74 | Variant68, 75 | Variant69, 76 | Variant70, 77 | Variant71, 78 | Variant72, 79 | Variant73, 80 | Variant74, 81 | Variant75, 82 | Variant76, 83 | Variant77, 84 | Variant78, 85 | Variant79, 86 | Variant80, 87 | Variant81, 88 | Variant82, 89 | Variant83, 90 | Variant84, 91 | Variant85, 92 | Variant86, 93 | Variant87, 94 | Variant88, 95 | Variant89, 96 | Variant90, 97 | Variant91, 98 | Variant92, 99 | Variant93, 100 | Variant94, 101 | Variant95, 102 | Variant96, 103 | Variant97, 104 | Variant98, 105 | Variant99, 106 | Variant100, 107 | Variant101, 108 | Variant102, 109 | Variant103, 110 | Variant104, 111 | Variant105, 112 | Variant106, 113 | Variant107, 114 | Variant108, 115 | Variant109, 116 | Variant110, 117 | Variant111, 118 | Variant112, 119 | Variant113, 120 | Variant114, 121 | Variant115, 122 | Variant116, 123 | Variant117, 124 | Variant118, 125 | Variant119, 126 | Variant120, 127 | Variant121, 128 | Variant122, 129 | Variant123, 130 | Variant124, 131 | Variant125, 132 | Variant126, 133 | Variant127, 134 | Variant128, 135 | Variant129, 136 | Variant130, 137 | Variant131, 138 | Variant132, 139 | Variant133, 140 | Variant134, 141 | Variant135, 142 | Variant136, 143 | Variant137, 144 | Variant138, 145 | Variant139, 146 | Variant140, 147 | Variant141, 148 | Variant142, 149 | Variant143, 150 | Variant144, 151 | Variant145, 152 | Variant146, 153 | Variant147, 154 | Variant148, 155 | Variant149, 156 | Variant150, 157 | Variant151, 158 | Variant152, 159 | Variant153, 160 | Variant154, 161 | Variant155, 162 | Variant156, 163 | Variant157, 164 | Variant158, 165 | Variant159, 166 | Variant160, 167 | Variant161, 168 | Variant162, 169 | Variant163, 170 | Variant164, 171 | Variant165, 172 | Variant166, 173 | Variant167, 174 | Variant168, 175 | Variant169, 176 | Variant170, 177 | Variant171, 178 | Variant172, 179 | Variant173, 180 | Variant174, 181 | Variant175, 182 | Variant176, 183 | Variant177, 184 | Variant178, 185 | Variant179, 186 | Variant180, 187 | Variant181, 188 | Variant182, 189 | Variant183, 190 | Variant184, 191 | Variant185, 192 | Variant186, 193 | Variant187, 194 | Variant188, 195 | Variant189, 196 | Variant190, 197 | Variant191, 198 | Variant192, 199 | Variant193, 200 | Variant194, 201 | Variant195, 202 | Variant196, 203 | Variant197, 204 | Variant198, 205 | Variant199, 206 | Variant200, 207 | Variant201, 208 | Variant202, 209 | Variant203, 210 | Variant204, 211 | Variant205, 212 | Variant206, 213 | Variant207, 214 | Variant208, 215 | Variant209, 216 | Variant210, 217 | Variant211, 218 | Variant212, 219 | Variant213, 220 | Variant214, 221 | Variant215, 222 | Variant216, 223 | Variant217, 224 | Variant218, 225 | Variant219, 226 | Variant220, 227 | Variant221, 228 | Variant222, 229 | Variant223, 230 | Variant224, 231 | Variant225, 232 | Variant226, 233 | Variant227, 234 | Variant228, 235 | Variant229, 236 | Variant230, 237 | Variant231, 238 | Variant232, 239 | Variant233, 240 | Variant234, 241 | Variant235, 242 | Variant236, 243 | Variant237, 244 | Variant238, 245 | Variant239, 246 | Variant240, 247 | Variant241, 248 | Variant242, 249 | Variant243, 250 | Variant244, 251 | Variant245, 252 | Variant246, 253 | Variant247, 254 | Variant248, 255 | Variant249, 256 | Variant250, 257 | Variant251, 258 | Variant252, 259 | Variant253, 260 | Variant254, 261 | Variant255, 262 | Variant256, 263 | Variant257, 264 | Variant258, 265 | Variant259, 266 | } 267 | #[repr(u32)] 268 | #[derive(Savefile, Debug, PartialEq)] 269 | enum U32Discriminant { 270 | Variant0 = 0, 271 | Variant1 = 1, 272 | Variant4294967295 = 4294967295, 273 | } 274 | 275 | #[test] 276 | fn test_enum_many_variants() { 277 | assert_roundtrip(ManyVariants::Variant0); 278 | assert_roundtrip(ManyVariants::Variant1); 279 | assert_roundtrip(ManyVariants::Variant255); 280 | assert_roundtrip(ManyVariants::Variant256); 281 | assert_roundtrip(ManyVariants::Variant259); 282 | } 283 | #[test] 284 | fn test_enum_u32_discriminant() { 285 | assert_roundtrip(U32Discriminant::Variant0); 286 | assert_roundtrip(U32Discriminant::Variant1); 287 | assert_roundtrip(U32Discriminant::Variant4294967295); 288 | } 289 | -------------------------------------------------------------------------------- /savefile-test/src/ext_benchmark.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | use std::hint::black_box; 3 | #[cfg(feature = "nightly")] 4 | use test::Bencher; 5 | 6 | mod savefile_test_bad_schema { 7 | use savefile::prelude::*; 8 | 9 | #[derive(Savefile, PartialEq, Debug)] 10 | struct Original { 11 | some_number: usize, 12 | a_few_strings: Vec, 13 | } 14 | 15 | #[derive(Savefile, PartialEq, Debug)] 16 | struct NewVersion { 17 | a_few_strings: Vec, 18 | some_number: usize, 19 | } 20 | 21 | #[test] 22 | #[should_panic( 23 | expected = "called `Result::unwrap()` on an `Err` value: IncompatibleSchema { message: \"Saved schema differs from in-memory schema for version 0. Error: At location [./Original/some_number]: In memory schema: vector, file schema: primitive\" }" 24 | )] 25 | fn test_schema_mismatch_savefile() { 26 | let original = Original { 27 | some_number: 0, 28 | a_few_strings: vec!["hello".to_string()], 29 | }; 30 | 31 | let encoded: Vec = save_to_mem(0, &original).unwrap(); 32 | let decoded: NewVersion = load_from_mem(&encoded[..], 0).unwrap(); 33 | println!("Savefile decoded: {:?}", decoded); 34 | } 35 | } 36 | 37 | mod bincode_test_bad_schema { 38 | use serde::{Deserialize, Serialize}; 39 | 40 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 41 | struct Original { 42 | some_number: usize, 43 | a_few_strings: Vec, 44 | } 45 | 46 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 47 | struct NewVersion { 48 | a_few_strings: Vec, 49 | some_number: usize, 50 | } 51 | 52 | #[test] 53 | fn test_schema_mismatch_bincode() { 54 | let original = Original { 55 | some_number: 0, 56 | a_few_strings: vec!["hello".to_string()], 57 | }; 58 | 59 | let encoded: Vec = bincode::serialize(&original).unwrap(); 60 | let decoded: NewVersion = bincode::deserialize(&encoded[..]).unwrap(); 61 | println!("Bincode decoded: {:?}", decoded); 62 | } 63 | } 64 | 65 | #[cfg(feature = "nightly")] 66 | mod bincode_benchmark { 67 | use serde::{Deserialize, Serialize}; 68 | use test::{black_box, Bencher}; 69 | 70 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 71 | struct Entity { 72 | x: f32, 73 | y: f32, 74 | } 75 | 76 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 77 | struct World(Vec); 78 | 79 | #[cfg(feature = "nightly")] 80 | #[bench] 81 | fn bench_ext_bincode(b: &mut Bencher) { 82 | let mut entities = Vec::new(); 83 | for _ in 0..100_000 { 84 | entities.push(Entity { x: 0.0, y: 4.0 }); 85 | entities.push(Entity { x: 10.0, y: 20.5 }); 86 | } 87 | let world = World(entities); 88 | b.iter(move || { 89 | let encoded: Vec = bincode::serialize(&world).unwrap(); 90 | 91 | // 8 bytes for the length of the vector, 4 bytes per float. 92 | //assert_eq!(encoded.len(), 8 + 4 * 4); 93 | 94 | let decoded: World = bincode::deserialize(&encoded[..]).unwrap(); 95 | 96 | //assert_eq!(world, decoded); 97 | decoded 98 | }) 99 | } 100 | } 101 | 102 | #[cfg(feature = "nightly")] 103 | mod savefile_benchmark { 104 | use savefile::prelude::*; 105 | use test::{black_box, Bencher}; 106 | 107 | #[derive(Savefile, PartialEq, Debug, Clone, Copy)] 108 | #[savefile_require_fast] 109 | #[repr(C)] 110 | struct Entity { 111 | x: f32, 112 | y: f32, 113 | } 114 | 115 | #[derive(Savefile, PartialEq, Debug)] 116 | struct World(Vec); 117 | 118 | #[cfg(feature = "nightly")] 119 | #[bench] 120 | fn bench_ext_savefile_with_reprc(b: &mut Bencher) { 121 | let mut entities = Vec::new(); 122 | for _ in 0..100_000 { 123 | entities.push(Entity { x: 0.0, y: 4.0 }); 124 | entities.push(Entity { x: 10.0, y: 20.5 }); 125 | } 126 | let world = World(entities); 127 | 128 | b.iter(move || { 129 | let mut encoded = Vec::new(); 130 | savefile::save(&mut encoded, 0, &world).unwrap(); 131 | 132 | let mut encoded_slice = &encoded[..]; 133 | let decoded: World = savefile::load::(&mut encoded_slice, 0).unwrap(); 134 | 135 | assert!((decoded.0.last().unwrap().x - 10.0).abs() < 1e-9); 136 | decoded 137 | }) 138 | } 139 | } 140 | 141 | #[cfg(feature = "nightly")] 142 | mod savefile_benchmark_no_reprc { 143 | use savefile::prelude::*; 144 | use test::{black_box, Bencher}; 145 | 146 | #[derive(Savefile, PartialEq, Debug, Clone, Copy)] 147 | struct Entity { 148 | x: f32, 149 | y: f32, 150 | } 151 | 152 | #[derive(Savefile, PartialEq, Debug)] 153 | struct World(Vec); 154 | 155 | #[cfg(feature = "nightly")] 156 | #[bench] 157 | fn bench_ext_savefile_no_reprc(b: &mut Bencher) { 158 | let mut entities = Vec::new(); 159 | for _ in 0..100_000 { 160 | entities.push(Entity { x: 0.0, y: 4.0 }); 161 | entities.push(Entity { x: 10.0, y: 20.5 }); 162 | } 163 | let world = World(entities); 164 | 165 | b.iter(move || { 166 | let mut encoded = Vec::new(); 167 | savefile::save(&mut encoded, 0, &world).unwrap(); 168 | 169 | let mut encoded_slice = &encoded[..]; 170 | let decoded: World = savefile::load::(&mut encoded_slice, 0).unwrap(); 171 | 172 | assert!((decoded.0.last().unwrap().x - 10.0).abs() < 1e-9); 173 | decoded 174 | }) 175 | } 176 | } 177 | 178 | #[derive(Savefile, PartialEq, Default)] 179 | pub struct Vector3 { 180 | pub x: f32, 181 | pub y: f32, 182 | pub z: f32, 183 | } 184 | #[derive(Savefile, PartialEq, Default)] 185 | pub struct Triangle { 186 | pub v0: Vector3, 187 | pub v1: Vector3, 188 | pub v2: Vector3, 189 | pub normal: Vector3, 190 | } 191 | 192 | #[derive(Savefile, PartialEq)] 193 | pub struct Mesh { 194 | pub triangles: Vec, 195 | } 196 | #[cfg(test)] 197 | pub fn generate_mesh() -> Mesh { 198 | let mut mesh = Mesh { triangles: vec![] }; 199 | const TRIANGLES: usize = 125_000; 200 | for _ in 0..TRIANGLES { 201 | mesh.triangles.push(Triangle::default()) 202 | } 203 | 204 | mesh 205 | } 206 | #[cfg(feature = "nightly")] 207 | #[bench] 208 | fn bench_ext_triangle(b: &mut Bencher) { 209 | let mesh = generate_mesh(); 210 | let mut encoded: Vec = Vec::new(); 211 | b.iter(move || { 212 | encoded.clear(); 213 | savefile::save_noschema(black_box(&mut encoded), 0, black_box(&mesh)).unwrap(); 214 | }) 215 | } 216 | #[test] 217 | fn test_triangle() { 218 | use savefile::Packed; 219 | assert!(unsafe { Triangle::repr_c_optimization_safe(0).is_yes() }); 220 | let mesh = generate_mesh(); 221 | 222 | let mut encoded = Vec::new(); 223 | encoded.clear(); 224 | savefile::save_noschema(black_box(&mut encoded), 0, black_box(&mesh)).unwrap(); 225 | } 226 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs: -------------------------------------------------------------------------------- 1 | use bytes::BufMut; 2 | use savefile_abi::AbiConnection; 3 | use savefile_abi::AbiExportable; 4 | use std::collections::HashMap; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::sync::{Arc, Mutex}; 8 | use std::task::{Context, Poll}; 9 | 10 | #[savefile_abi_exportable(version = 0)] 11 | pub trait SimpleInterface { 12 | fn do_call(&self, x: u32) -> u32; 13 | } 14 | #[savefile_abi_exportable(version = 0)] 15 | pub trait AdvancedTestInterface: Send { 16 | fn roundtrip_hashmap(&self, x: HashMap) -> HashMap; 17 | fn clone_hashmap(&self, x: &HashMap) -> HashMap; 18 | 19 | fn return_trait_object(&self) -> Box; 20 | fn test_slices(&mut self, slice: &[u32]) -> u32; 21 | 22 | fn return_boxed_closure(&self) -> Box u32>; 23 | fn return_boxed_closure2(&self) -> Box; 24 | fn many_callbacks(&mut self, x: &mut dyn FnMut(&dyn Fn(&dyn Fn() -> u32) -> u32) -> u32) -> u32; 25 | 26 | fn buf_callback(&mut self, cb: Box); 27 | fn return_boxed_closure_result(&self, fail: bool) -> Result u32>, ()>; 28 | fn owned_boxed_closure_param(&self, owned: Box u32>); 29 | 30 | fn pinned_self(self: Pin<&mut Self>, arg: u32) -> u32; 31 | fn boxed_future(&self) -> Pin>>; 32 | 33 | fn bufmut(&self, buf: &mut dyn BufMut); 34 | } 35 | 36 | struct SimpleImpl; 37 | 38 | impl Drop for SimpleImpl { 39 | fn drop(&mut self) { 40 | println!("Dropping impl") 41 | } 42 | } 43 | impl SimpleInterface for SimpleImpl { 44 | fn do_call(&self, x: u32) -> u32 { 45 | println!("do_call running"); 46 | x 47 | } 48 | } 49 | struct AdvancedTestInterfaceImpl {} 50 | 51 | impl AdvancedTestInterface for AdvancedTestInterfaceImpl { 52 | fn roundtrip_hashmap(&self, x: HashMap) -> HashMap { 53 | x 54 | } 55 | 56 | fn clone_hashmap(&self, x: &HashMap) -> HashMap { 57 | x.clone() 58 | } 59 | 60 | fn return_trait_object(&self) -> Box { 61 | Box::new(SimpleImpl) 62 | } 63 | 64 | fn return_boxed_closure(&self) -> Box u32> { 65 | Box::new(|| 42) 66 | } 67 | fn return_boxed_closure2(&self) -> Box { 68 | Box::new(|| {}) 69 | } 70 | 71 | fn test_slices(&mut self, slice: &[u32]) -> u32 { 72 | slice.iter().copied().sum() 73 | } 74 | 75 | fn many_callbacks(&mut self, x: &mut dyn FnMut(&dyn Fn(&dyn Fn() -> u32) -> u32) -> u32) -> u32 { 76 | x(&|y| y()) 77 | } 78 | 79 | fn buf_callback(&mut self, cb: Box) { 80 | cb(&[1, 2, 3], "hello".to_string()) 81 | } 82 | fn return_boxed_closure_result(&self, fail: bool) -> Result u32>, ()> { 83 | if fail { 84 | Err(()) 85 | } else { 86 | Ok(Box::new(|| 42)) 87 | } 88 | } 89 | 90 | fn owned_boxed_closure_param(&self, owned: Box u32>) { 91 | assert_eq!(owned(), 42); 92 | } 93 | fn pinned_self(self: Pin<&mut Self>, arg: u32) -> u32 { 94 | arg 95 | } 96 | fn boxed_future(&self) -> Pin>> { 97 | Box::pin(async move { 98 | tokio::time::sleep(std::time::Duration::from_millis(1)).await; 99 | 42 100 | }) 101 | } 102 | fn bufmut(&self, buf: &mut dyn bytes::BufMut) { 103 | for x in 0..100 { 104 | buf.put_u8(x as u8); 105 | } 106 | } 107 | } 108 | 109 | struct TestUser(Box); 110 | 111 | pub trait DummyTrait2: Send {} 112 | 113 | impl DummyTrait2 for TestUser {} 114 | fn require_send(_t: T) {} 115 | #[test] 116 | fn abi_test_buf_send() { 117 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 118 | require_send(boxed); 119 | } 120 | 121 | #[test] 122 | fn test_trait_object_in_return_position() { 123 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 124 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 125 | 126 | let ret = conn.return_boxed_closure_result(false); 127 | assert_eq!(ret.unwrap()(), 42); 128 | let ret = conn.return_boxed_closure_result(true); 129 | let Err(()) = ret else { panic!("Expected Err") }; 130 | } 131 | 132 | #[test] 133 | fn abi_test_buf_callback() { 134 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 135 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 136 | let buf = Arc::new(Mutex::new(None)); 137 | let bufclone = Arc::clone(&buf); 138 | conn.buf_callback(Box::new(move |argbuf, _s| { 139 | *bufclone.lock().unwrap() = Some(argbuf.to_vec()); 140 | })); 141 | let mut guard = buf.lock().unwrap(); 142 | let vec = guard.take().unwrap(); 143 | assert_eq!(vec, [1, 2, 3]); 144 | } 145 | #[test] 146 | fn abi_test_slice() { 147 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 148 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 149 | 150 | assert!(conn.get_arg_passable_by_ref("test_slices", 0)); 151 | assert_eq!(conn.test_slices(&[1, 2, 3, 4]), 10); 152 | } 153 | 154 | #[test] 155 | fn test_result_trait_object_in_return_position() { 156 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 157 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 158 | 159 | let ret = conn.return_trait_object(); 160 | assert_eq!(ret.do_call(42), 42); 161 | assert_eq!(ret.do_call(42), 42); 162 | } 163 | 164 | #[tokio::test] 165 | async fn test_boxed_future() { 166 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 167 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 168 | println!("Before timeout"); 169 | 170 | let fut = conn.boxed_future(); 171 | 172 | fut.await; 173 | println!("After timeout"); 174 | } 175 | 176 | #[test] 177 | fn test_boxed_trait_object_in_arg_position() { 178 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 179 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 180 | 181 | conn.owned_boxed_closure_param(Box::new(|| 42)); 182 | } 183 | #[test] 184 | fn test_return_boxed_closure() { 185 | let closure; 186 | let closure2; 187 | { 188 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 189 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 190 | 191 | closure = conn.return_boxed_closure(); 192 | closure2 = conn.return_boxed_closure2(); 193 | assert_eq!(closure(), 42); 194 | } 195 | assert_eq!(closure(), 42); 196 | closure2(); 197 | } 198 | 199 | #[test] 200 | fn test_call_many_callbacks() { 201 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 202 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 203 | assert_eq!( 204 | conn.many_callbacks(&mut |x| { 205 | x(&|| { 206 | println!("In the inner sanctum!"); 207 | 42 208 | }) 209 | }), 210 | 42 211 | ); 212 | } 213 | #[test] 214 | fn test_advanced_abi2() { 215 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 216 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 217 | 218 | let mut mymap = HashMap::new(); 219 | mymap.insert("mascot".to_string(), "ferris".to_string()); 220 | mymap.insert("concurrency".to_string(), "fearless".to_string()); 221 | let mymap = conn.roundtrip_hashmap(mymap); 222 | 223 | let mymap2: HashMap = conn.clone_hashmap(&mymap); 224 | 225 | assert!(mymap2.contains_key("mascot")); 226 | assert_eq!(mymap2["mascot"], "ferris"); 227 | } 228 | #[test] 229 | fn test_buf_mut() { 230 | let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); 231 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 232 | 233 | let mut vec = vec![]; 234 | conn.bufmut(&mut vec); 235 | for i in 0..100u8 { 236 | assert_eq!(vec[i as usize], i); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs: -------------------------------------------------------------------------------- 1 | use crate::savefile_abi_test::argument_backward_compatibility::v1::{ArgInterfaceV1, EnumArgument, Implementation1}; 2 | use crate::savefile_abi_test::argument_backward_compatibility::v2::{ArgInterfaceV2, Implementation2}; 3 | use crate::savefile_abi_test::basic_abi_tests::CowSmuggler; 4 | use savefile::prelude::AbiRemoved; 5 | use savefile::{get_schema, SavefileError, WithSchemaContext}; 6 | use savefile_abi::RawAbiCallResult::AbiError; 7 | use savefile_abi::{verify_compatiblity, AbiConnection, AbiExportable}; 8 | use savefile_derive::Savefile; 9 | 10 | mod v1 { 11 | #[derive(Savefile)] 12 | pub struct Argument { 13 | pub data1: u32, 14 | pub data2: u32, 15 | } 16 | 17 | #[derive(Savefile)] 18 | pub enum EnumArgument { 19 | Variant1, 20 | Variant2, 21 | } 22 | 23 | #[savefile_abi_exportable(version = 0)] 24 | pub trait ArgInterfaceV1 { 25 | fn sums(&self, a: Argument, b: Argument) -> u32; 26 | fn enum_arg(&self, a: EnumArgument) -> String; 27 | fn function_existing_in_v1(&self); 28 | fn closure_test(&self, f: Box); 29 | } 30 | #[derive(Default)] 31 | pub struct Implementation1 {} 32 | 33 | impl ArgInterfaceV1 for Implementation1 { 34 | fn sums(&self, a: Argument, b: Argument) -> u32 { 35 | a.data1 + a.data2 + b.data1 + b.data2 36 | } 37 | fn enum_arg(&self, a: EnumArgument) -> String { 38 | match a { 39 | EnumArgument::Variant1 => "Variant1".into(), 40 | EnumArgument::Variant2 => "Variant2".into(), 41 | } 42 | } 43 | fn function_existing_in_v1(&self) {} 44 | fn closure_test(&self, f: Box) { 45 | f() 46 | } 47 | } 48 | } 49 | 50 | mod v2 { 51 | use savefile::prelude::*; 52 | use savefile::AbiRemoved; 53 | use savefile_derive::Savefile; 54 | 55 | #[derive(Savefile, Debug)] 56 | pub struct ArgArgument { 57 | #[savefile_versions = "0..0"] 58 | pub data1: AbiRemoved, 59 | pub data2: u32, 60 | #[savefile_versions = "1.."] 61 | pub data3: u32, 62 | } 63 | #[derive(Savefile)] 64 | pub enum EnumArgument { 65 | Variant1, 66 | Variant2, 67 | #[savefile_versions = "1.."] 68 | Variant3, 69 | } 70 | 71 | #[savefile_abi_exportable(version = 1)] 72 | pub trait ArgInterfaceV2 { 73 | fn sums(&self, a: ArgArgument, b: ArgArgument) -> u32; 74 | fn enum_arg(&self, a: EnumArgument) -> String { 75 | match a { 76 | EnumArgument::Variant1 => "Variant1".into(), 77 | EnumArgument::Variant2 => "Variant2".into(), 78 | EnumArgument::Variant3 => "Variant3".into(), 79 | } 80 | } 81 | fn function_existing_in_v2(&self); 82 | fn closure_test(&self, f: Box); 83 | } 84 | 85 | #[derive(Default)] 86 | pub struct Implementation2 {} 87 | impl ArgInterfaceV2 for Implementation2 { 88 | fn sums(&self, a: ArgArgument, b: ArgArgument) -> u32 { 89 | a.data3 + a.data2 + b.data2 + b.data3 90 | } 91 | 92 | fn function_existing_in_v2(&self) {} 93 | fn closure_test(&self, f: Box) { 94 | f() 95 | } 96 | } 97 | } 98 | 99 | #[test] 100 | #[cfg(not(miri))] 101 | pub fn test_abi_schemas_get_def() { 102 | let exportable = ::get_definition(0); 103 | insta::assert_yaml_snapshot!(exportable); 104 | } 105 | 106 | #[test] 107 | #[cfg(not(miri))] 108 | pub fn test_backward_compatibility() -> Result<(), SavefileError> { 109 | verify_compatiblity::("schemas") 110 | } 111 | 112 | #[test] 113 | pub fn test_arg_argument_metadata() { 114 | use savefile::WithSchema; 115 | let schema = get_schema::(0); 116 | println!("Schema: {:#?}", schema); 117 | assert!(!schema.layout_compatible(&schema)); //Versions containing removed items should never be considered layout compatible (since their schema type is not identical to the memory type) 118 | } 119 | 120 | #[test] 121 | pub fn test_caller_has_older_version() { 122 | let iface2: Box = Box::new(Implementation2 {}); 123 | assert_eq!( 124 | iface2.sums( 125 | v2::ArgArgument { 126 | data2: 3, 127 | data3: 2, 128 | data1: AbiRemoved::new() 129 | }, 130 | v2::ArgArgument { 131 | data2: 3, 132 | data3: 2, 133 | data1: AbiRemoved::new() 134 | } 135 | ), 136 | 10 137 | ); 138 | 139 | let conn1 = unsafe { 140 | AbiConnection::::from_boxed_trait_for_test( 141 | ::ABI_ENTRY, 142 | iface2, 143 | ) 144 | } 145 | .unwrap(); 146 | 147 | let s = conn1.sums(v1::Argument { data1: 2, data2: 3 }, v1::Argument { data1: 4, data2: 5 }); 148 | println!("Sum: {}", s); 149 | assert_eq!(s, 8); //Because implementation expects data2 and data3, but we're only sending data2. 150 | 151 | assert_eq!(conn1.enum_arg(EnumArgument::Variant1), "Variant1".to_string()); 152 | } 153 | 154 | #[test] 155 | pub fn test_caller_has_newer_version() { 156 | let iface1: Box = Box::new(Implementation1 {}); 157 | let conn1 = unsafe { 158 | AbiConnection::::from_boxed_trait_for_test( 159 | ::ABI_ENTRY, 160 | iface1, 161 | ) 162 | } 163 | .unwrap(); 164 | 165 | assert_eq!( 166 | conn1.sums( 167 | v2::ArgArgument { 168 | data1: AbiRemoved::new(), 169 | data2: 1, 170 | data3: 2 171 | }, 172 | v2::ArgArgument { 173 | data1: AbiRemoved::new(), 174 | data2: 3, 175 | data3: 4 176 | }, 177 | ), 178 | 4 179 | ); //Because implementation expects data1 and data2, but we're only sending data2. 180 | 181 | assert_eq!(conn1.enum_arg(v2::EnumArgument::Variant1), "Variant1".to_string()); 182 | 183 | conn1.closure_test(Box::new(|| {})); 184 | } 185 | 186 | #[test] 187 | #[should_panic(expected = "Enum EnumArgument, variant Variant3 is not present in version 0")] 188 | pub fn test_caller_has_newer_version_and_uses_enum_that_callee_doesnt_have() { 189 | let iface1: Box = Box::new(Implementation1 {}); 190 | let conn1 = unsafe { 191 | AbiConnection::::from_boxed_trait_for_test( 192 | ::ABI_ENTRY, 193 | iface1, 194 | ) 195 | } 196 | .unwrap(); 197 | 198 | assert_eq!(conn1.enum_arg(v2::EnumArgument::Variant3), "Variant3".to_string()); 199 | } 200 | 201 | #[test] 202 | #[should_panic(expected = "'function_existing_in_v2' does not exist in implementation.")] 203 | pub fn test_caller_has_newer_version_calling_non_existing_function() { 204 | let iface1: Box = Box::new(Implementation1 {}); 205 | let conn1 = unsafe { 206 | AbiConnection::::from_boxed_trait_for_test( 207 | ::ABI_ENTRY, 208 | iface1, 209 | ) 210 | } 211 | .unwrap(); 212 | conn1.function_existing_in_v2(); 213 | } 214 | 215 | #[test] 216 | #[should_panic(expected = "'function_existing_in_v1' does not exist in implementation.")] 217 | pub fn test_caller_has_older_version_calling_non_existing_function() { 218 | let iface2: Box = Box::new(Implementation2 {}); 219 | let conn = unsafe { 220 | AbiConnection::::from_boxed_trait_for_test( 221 | ::ABI_ENTRY, 222 | iface2, 223 | ) 224 | } 225 | .unwrap(); 226 | conn.function_existing_in_v1(); 227 | } 228 | #[test] 229 | fn test_calling_function_that_is_later_removed() { 230 | let boxed: Box = Box::new(Implementation1 {}); 231 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 232 | conn.function_existing_in_v1(); 233 | } 234 | #[test] 235 | fn test_calling_function_that_is_added_in_later_version() { 236 | let boxed: Box = Box::new(Implementation2 {}); 237 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 238 | conn.function_existing_in_v2(); 239 | } 240 | -------------------------------------------------------------------------------- /savefile-test/src/test_versioning.rs: -------------------------------------------------------------------------------- 1 | use savefile::prelude::*; 2 | 3 | #[derive(Debug, PartialEq, Savefile)] 4 | struct Version1 { 5 | a: String, 6 | b: Vec, 7 | c: usize, 8 | } 9 | #[derive(Debug, PartialEq, Savefile)] 10 | struct Version2 { 11 | a: String, 12 | #[savefile_versions = "0..0"] 13 | b: Removed>, 14 | #[savefile_default_val = "123"] 15 | #[savefile_versions = "1.."] 16 | newb: u32, 17 | c: usize, 18 | } 19 | 20 | #[derive(Debug, PartialEq, Savefile, Clone)] 21 | struct Version3 { 22 | a: String, 23 | #[savefile_versions = "0..0"] 24 | b: Removed>, 25 | #[savefile_versions = "1..1"] 26 | newb: u32, 27 | c: usize, 28 | #[savefile_versions = "2.."] 29 | d: usize, 30 | } 31 | use quickcheck::{Arbitrary, Gen}; 32 | 33 | impl Arbitrary for Version3 { 34 | fn arbitrary(g: &mut Gen) -> Version3 { 35 | Version3 { 36 | a: String::arbitrary(g), 37 | b: Removed::new(), 38 | newb: 0, 39 | c: usize::arbitrary(g), 40 | d: usize::arbitrary(g), 41 | } 42 | } 43 | } 44 | 45 | #[quickcheck] 46 | #[cfg(not(miri))] 47 | fn test_quickcheck_version3(xs: Version3) -> bool { 48 | xs == roundtrip_version(xs.clone(), 2) 49 | } 50 | 51 | #[test] 52 | fn simple_vertest1() { 53 | use crate::assert_roundtrip_to_new_version; 54 | let ver2: Version2 = assert_roundtrip_to_new_version( 55 | Version1 { 56 | a: "Hello".to_string(), 57 | b: vec!["a".to_string(), "b".to_string()], 58 | c: 412, 59 | }, 60 | 0, 61 | Version2 { 62 | a: "Hello".to_string(), 63 | b: Removed::new(), 64 | newb: 123, 65 | c: 412, 66 | }, 67 | 1, 68 | ); 69 | 70 | assert_roundtrip_to_new_version( 71 | ver2, 72 | 1, 73 | Version3 { 74 | a: "Hello".to_string(), 75 | b: Removed::new(), 76 | newb: 123, 77 | c: 412, 78 | d: 0, 79 | }, 80 | 2, 81 | ); 82 | } 83 | 84 | #[derive(Debug, PartialEq, Savefile)] 85 | enum EnumVer1 { 86 | Variant1, 87 | Variant2, 88 | } 89 | 90 | #[derive(Debug, PartialEq, Savefile)] 91 | enum EnumVer2 { 92 | Variant1, 93 | Variant2, 94 | #[savefile_versions = "1.."] 95 | Variant3, 96 | } 97 | 98 | #[test] 99 | fn test_versioning_of_enums() { 100 | use crate::assert_roundtrip_to_new_version; 101 | assert_roundtrip_to_new_version(EnumVer1::Variant1, 0, EnumVer2::Variant1, 1); 102 | assert_roundtrip_to_new_version(EnumVer1::Variant2, 0, EnumVer2::Variant2, 1); 103 | } 104 | 105 | #[derive(Debug, PartialEq, Savefile)] 106 | enum EnumVerA1 { 107 | Variant1, 108 | Variant2 { x: u32, y: u32 }, 109 | } 110 | 111 | #[derive(Debug, PartialEq, Savefile)] 112 | enum EnumVerA2 { 113 | Variant1, 114 | Variant2 { 115 | x: u32, 116 | #[savefile_versions = "0..0"] 117 | y: Removed, 118 | }, 119 | } 120 | 121 | #[test] 122 | fn test_versioning_of_enums2() { 123 | use crate::assert_roundtrip_to_new_version; 124 | assert_roundtrip_to_new_version( 125 | EnumVerA1::Variant2 { x: 32, y: 33 }, 126 | 0, 127 | EnumVerA2::Variant2 { 128 | x: 32, 129 | y: Removed::new(), 130 | }, 131 | 1, 132 | ); 133 | } 134 | 135 | #[derive(Debug, PartialEq, Savefile)] 136 | enum EnumVerB1 { 137 | Variant1, 138 | Variant2(u32, u32), 139 | } 140 | 141 | #[derive(Debug, PartialEq, Savefile)] 142 | enum EnumVerB2 { 143 | Variant1, 144 | Variant2(u32, #[savefile_versions = "0..0"] Removed), 145 | } 146 | 147 | #[test] 148 | fn test_versioning_of_enums3() { 149 | use crate::assert_roundtrip_to_new_version; 150 | assert_roundtrip_to_new_version( 151 | EnumVerB1::Variant2(32, 33), 152 | 0, 153 | EnumVerB2::Variant2(32, Removed::new()), 154 | 1, 155 | ); 156 | } 157 | 158 | #[derive(Debug, PartialEq, Savefile)] 159 | struct SubSubData1 { 160 | x: u32, 161 | } 162 | #[derive(Debug, PartialEq, Savefile)] 163 | struct SubData1 { 164 | some_sub: SubSubData1, 165 | } 166 | #[derive(Debug, PartialEq, Savefile)] 167 | struct ComplexData1 { 168 | some_field: SubData1, 169 | } 170 | 171 | #[derive(Debug, PartialEq, Savefile)] 172 | struct SubSubData2 { 173 | y: u32, 174 | } 175 | #[derive(Debug, PartialEq, Savefile)] 176 | struct SubData2 { 177 | some_sub: SubSubData2, 178 | } 179 | #[derive(Debug, PartialEq, Savefile)] 180 | struct ComplexData2 { 181 | some_field: SubData2, 182 | } 183 | 184 | #[test] 185 | fn test_versioning_of_enums4() { 186 | use crate::assert_roundtrip_to_new_version; 187 | assert_roundtrip_to_new_version( 188 | ComplexData1 { 189 | some_field: SubData1 { 190 | some_sub: SubSubData1 { x: 43 }, 191 | }, 192 | }, 193 | 0, 194 | ComplexData2 { 195 | some_field: SubData2 { 196 | some_sub: SubSubData2 { y: 43 }, 197 | }, 198 | }, 199 | 1, 200 | ); 201 | } 202 | 203 | #[derive(Debug, PartialEq, Savefile, Default)] 204 | enum DefTraitEnum { 205 | #[default] 206 | VariantA, 207 | VariantB, 208 | VariantC, 209 | } 210 | 211 | #[derive(Debug, PartialEq, Savefile)] 212 | struct DefTraitTest { 213 | #[savefile_versions = "1.."] 214 | removed_enum: DefTraitEnum, 215 | } 216 | 217 | #[test] 218 | fn test_default_trait1() { 219 | use crate::assert_roundtrip_version; 220 | assert_roundtrip_version::( 221 | DefTraitTest { 222 | removed_enum: DefTraitEnum::VariantA, 223 | }, 224 | 1, 225 | true, 226 | ); 227 | } 228 | 229 | #[test] 230 | fn test_custom_default_fn() { 231 | #[derive(Debug, PartialEq, Savefile)] 232 | struct VersionB1 { 233 | a: String, 234 | } 235 | 236 | fn b_default() -> String { 237 | "custom_default_value".to_string() 238 | } 239 | #[derive(Debug, PartialEq, Savefile)] 240 | struct VersionB2 { 241 | a: String, 242 | #[savefile_default_fn = "b_default"] 243 | #[savefile_versions = "1.."] 244 | b: String, 245 | } 246 | 247 | use crate::assert_roundtrip_to_new_version; 248 | assert_roundtrip_to_new_version( 249 | VersionB1 { a: "test".to_string() }, 250 | 0, 251 | VersionB2 { 252 | a: "test".to_string(), 253 | b: "custom_default_value".to_string(), 254 | }, 255 | 1, 256 | ); 257 | } 258 | 259 | #[derive(Debug, PartialEq, Savefile)] 260 | struct StructWithOneType { 261 | a_str: String, 262 | } 263 | 264 | #[derive(Debug, PartialEq, Savefile)] 265 | struct AnewType { 266 | an_u32: u32, 267 | } 268 | 269 | use crate::{roundtrip, roundtrip_version}; 270 | use std::convert::From; 271 | 272 | impl From for AnewType { 273 | fn from(_dummy: String) -> AnewType { 274 | AnewType { an_u32: 9999 } 275 | } 276 | } 277 | 278 | #[derive(Debug, PartialEq, Savefile)] 279 | struct StructWithAnotherType { 280 | #[savefile_versions_as = "0..0:String"] 281 | #[savefile_versions = "1.."] 282 | a_str: AnewType, 283 | } 284 | 285 | #[test] 286 | fn test_change_type_of_field() { 287 | use crate::assert_roundtrip_to_new_version; 288 | assert_roundtrip_to_new_version( 289 | StructWithOneType { 290 | a_str: "test".to_string(), 291 | }, 292 | 0, 293 | StructWithAnotherType { 294 | a_str: AnewType { an_u32: 9999 }, 295 | }, 296 | 1, 297 | ); 298 | } 299 | 300 | fn convert2newtype(s: String) -> AnewType { 301 | AnewType { 302 | an_u32: s.parse().unwrap(), 303 | } 304 | } 305 | #[derive(Debug, PartialEq, Savefile)] 306 | struct StructWithAnotherType2 { 307 | #[savefile_versions_as = "0..0:convert2newtype:String"] 308 | #[savefile_versions = "1.."] 309 | a_str: AnewType, 310 | } 311 | 312 | #[test] 313 | fn test_change_type_of_field2() { 314 | use crate::assert_roundtrip_to_new_version; 315 | assert_roundtrip_to_new_version( 316 | StructWithOneType { 317 | a_str: "422".to_string(), 318 | }, 319 | 0, 320 | StructWithAnotherType2 { 321 | a_str: AnewType { an_u32: 422 }, 322 | }, 323 | 1, 324 | ); 325 | } 326 | 327 | #[derive(Debug, PartialEq, Savefile)] 328 | struct FastVersionA0 { 329 | a: u32, 330 | b: u32, 331 | c: u32, 332 | } 333 | #[derive(Debug, PartialEq, Savefile)] 334 | struct FastVersionA1 { 335 | a: u32, 336 | #[savefile_versions = "0..0"] 337 | b: Removed, 338 | c: u32, 339 | } 340 | 341 | #[test] 342 | fn simple_vertest_a() { 343 | use crate::assert_roundtrip_to_new_version; 344 | let _ver1: FastVersionA1 = assert_roundtrip_to_new_version( 345 | FastVersionA0 { a: 2, b: 3, c: 4 }, 346 | 0, 347 | FastVersionA1 { 348 | a: 2, 349 | b: Removed::new(), 350 | c: 4, 351 | }, 352 | 1, 353 | ); 354 | } 355 | 356 | #[derive(Debug, PartialEq, Savefile)] 357 | struct FastVersionB0 { 358 | a: u32, 359 | b: u32, 360 | } 361 | #[derive(Debug, PartialEq, Savefile)] 362 | struct FastVersionB1 { 363 | a: u32, 364 | b: u32, 365 | #[savefile_versions = "1.."] 366 | c: u32, 367 | } 368 | 369 | #[test] 370 | fn simple_vertest_b() { 371 | use crate::assert_roundtrip_to_new_version; 372 | let _ver1: FastVersionB1 = 373 | assert_roundtrip_to_new_version(FastVersionB0 { a: 2, b: 3 }, 0, FastVersionB1 { a: 2, b: 3, c: 0 }, 1); 374 | } 375 | -------------------------------------------------------------------------------- /savefile-derive/src/serialize.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use syn::{Attribute, DeriveInput}; 3 | 4 | use crate::common::{get_extra_where_clauses, parse_attr_tag, FieldInfo}; 5 | use crate::{doc_hidden, get_enum_size}; 6 | use crate::implement_fields_serialize; 7 | use syn::spanned::Spanned; 8 | 9 | pub(super) fn savefile_derive_crate_serialize(input: DeriveInput) -> TokenStream { 10 | let name = &input.ident; 11 | let name_str = name.to_string(); 12 | 13 | let generics = &input.generics; 14 | 15 | let doc_hidden = doc_hidden(&input.attrs); 16 | 17 | let span = proc_macro2::Span::call_site(); 18 | let defspan = proc_macro2::Span::call_site(); 19 | 20 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 21 | let extra_where = get_extra_where_clauses( 22 | &input, 23 | where_clause, 24 | quote! {_savefile::prelude::Serialize + _savefile::prelude::Packed}, 25 | ); 26 | 27 | let uses = quote_spanned! { defspan => 28 | extern crate savefile as _savefile; 29 | }; 30 | 31 | let serialize = quote_spanned! {defspan=> 32 | _savefile::prelude::Serialize 33 | }; 34 | let serializer = quote_spanned! {defspan=> 35 | _savefile::prelude::Serializer 36 | }; 37 | let saveerr = quote_spanned! {defspan=> 38 | Result<(),_savefile::prelude::SavefileError> 39 | }; 40 | 41 | let dummy_const = syn::Ident::new("_", proc_macro2::Span::call_site()); 42 | 43 | let expanded = match &input.data { 44 | &syn::Data::Enum(ref enum1) => { 45 | let mut output = Vec::new(); 46 | let enum_size = get_enum_size(&input.attrs, enum1.variants.len()); 47 | 48 | for (var_idx_usize, variant) in enum1.variants.iter().enumerate() { 49 | let var_idx_u8: u8 = var_idx_usize as u8; 50 | let var_idx_u16: u16 = var_idx_usize as u16; 51 | let var_idx_u32: u32 = var_idx_usize as u32; 52 | 53 | let verinfo = parse_attr_tag(&variant.attrs); 54 | let (field_from_version, field_to_version) = (verinfo.version_from, verinfo.version_to); 55 | 56 | let variant_serializer = match enum_size.discriminant_size { 57 | 1 => quote! { serializer.write_u8(#var_idx_u8)? ; }, 58 | 2 => quote! { serializer.write_u16(#var_idx_u16)? ; }, 59 | 4 => quote! { serializer.write_u32(#var_idx_u32)? ; }, 60 | _ => unreachable!(), 61 | }; 62 | 63 | let var_ident = (variant.ident).clone(); 64 | let variant_name = quote! { #name::#var_ident }; 65 | let variant_name_str = var_ident.to_string(); 66 | let variant_name_spanned = quote_spanned! { span => #variant_name}; 67 | match &variant.fields { 68 | &syn::Fields::Named(ref fields_named) => { 69 | let field_infos: Vec = fields_named 70 | .named 71 | .iter() 72 | .enumerate() 73 | .map(|(field_index, field)| FieldInfo { 74 | ident: Some(field.ident.clone().expect("Expected identifier[4]")), 75 | field_span: field.ident.as_ref().unwrap().span(), 76 | index: field_index as u32, 77 | ty: &field.ty, 78 | attrs: &field.attrs, 79 | }) 80 | .collect(); 81 | 82 | let (fields_serialized, fields_names) = 83 | implement_fields_serialize(field_infos, false, false /*we've invented real names*/); 84 | output.push(quote!( #variant_name_spanned{#(#fields_names,)*} => { 85 | if serializer.file_version < #field_from_version || serializer.file_version > #field_to_version { 86 | panic!("Enum {}, variant {} is not present in version {}", #name_str, #variant_name_str, serializer.file_version); 87 | } 88 | #variant_serializer 89 | #fields_serialized 90 | } )); 91 | } 92 | &syn::Fields::Unnamed(ref fields_unnamed) => { 93 | let field_infos: Vec = fields_unnamed 94 | .unnamed 95 | .iter() 96 | .enumerate() 97 | .map(|(idx, field)| FieldInfo { 98 | field_span: field.ty.span(), 99 | ident: Some(syn::Ident::new( 100 | // We bind the tuple field to a real name, like x0, x1 etc. 101 | &("x".to_string() + &idx.to_string()), 102 | Span::call_site(), 103 | )), 104 | index: idx as u32, 105 | ty: &field.ty, 106 | attrs: &field.attrs, 107 | }) 108 | .collect(); 109 | 110 | let (fields_serialized, fields_names) = 111 | implement_fields_serialize(field_infos, false, false /*we've invented real names*/); 112 | 113 | output.push( 114 | quote!( 115 | 116 | #variant_name_spanned(#(#fields_names,)*) => { 117 | if serializer.file_version < #field_from_version || serializer.file_version > #field_to_version { 118 | panic!("Enum {}, variant {} is not present in version {}", #name_str, #variant_name_str, serializer.file_version); 119 | } 120 | #variant_serializer ; #fields_serialized 121 | } 122 | ), 123 | ); 124 | } 125 | &syn::Fields::Unit => { 126 | output.push(quote!( #variant_name_spanned => { 127 | if serializer.file_version < #field_from_version || serializer.file_version > #field_to_version { 128 | panic!("Enum {}, variant {} is not present in version {}", #name_str, #variant_name_str, serializer.file_version); 129 | } 130 | #variant_serializer ; } )); 131 | } 132 | } 133 | } 134 | quote! { 135 | #[allow(non_upper_case_globals)] 136 | #[allow(clippy::double_comparisons)] 137 | #[allow(clippy::manual_range_contains)] 138 | const #dummy_const: () = { 139 | #uses 140 | 141 | #[automatically_derived] 142 | #doc_hidden 143 | impl #impl_generics #serialize for #name #ty_generics #where_clause #extra_where { 144 | 145 | #[allow(unused_comparisons, unused_variables)] 146 | fn serialize(&self, serializer: &mut #serializer) -> #saveerr { 147 | match self { 148 | #(#output,)* 149 | } 150 | Ok(()) 151 | } 152 | } 153 | }; 154 | } 155 | } 156 | &syn::Data::Struct(ref struc) => { 157 | let fields_serialize: TokenStream; 158 | let _field_names: Vec; 159 | match &struc.fields { 160 | &syn::Fields::Named(ref namedfields) => { 161 | let field_infos: Vec = namedfields 162 | .named 163 | .iter() 164 | .enumerate() 165 | .map(|(field_index, field)| FieldInfo { 166 | ident: Some(field.ident.clone().expect("Identifier[5]")), 167 | field_span: field.ident.as_ref().unwrap().span(), 168 | ty: &field.ty, 169 | index: field_index as u32, 170 | attrs: &field.attrs, 171 | }) 172 | .collect(); 173 | 174 | let t = implement_fields_serialize(field_infos, true, false); 175 | fields_serialize = t.0; 176 | _field_names = t.1; 177 | } 178 | &syn::Fields::Unnamed(ref fields_unnamed) => { 179 | let field_infos: Vec = fields_unnamed 180 | .unnamed 181 | .iter() 182 | .enumerate() 183 | .map(|(field_index, field)| FieldInfo { 184 | field_span: field.ty.span(), 185 | ident: None, 186 | ty: &field.ty, 187 | index: field_index as u32, 188 | attrs: &field.attrs, 189 | }) 190 | .collect(); 191 | 192 | let t = implement_fields_serialize(field_infos, true, true); 193 | fields_serialize = t.0; 194 | _field_names = t.1; 195 | } 196 | &syn::Fields::Unit => { 197 | _field_names = Vec::new(); 198 | fields_serialize = quote! { {} }; 199 | } 200 | } 201 | quote! { 202 | #[allow(non_upper_case_globals)] 203 | #[allow(clippy::double_comparisons)] 204 | #[allow(clippy::manual_range_contains)] 205 | const #dummy_const: () = { 206 | #uses 207 | 208 | #[automatically_derived] 209 | #doc_hidden 210 | impl #impl_generics #serialize for #name #ty_generics #where_clause #extra_where { 211 | #[allow(unused_comparisons, unused_variables)] 212 | fn serialize(&self, serializer: &mut #serializer) -> #saveerr { 213 | #fields_serialize 214 | Ok(()) 215 | } 216 | } 217 | }; 218 | } 219 | } 220 | _ => { 221 | abort_call_site!("Unsupported data type"); 222 | } 223 | }; 224 | 225 | expanded 226 | } 227 | -------------------------------------------------------------------------------- /savefile-test/src/savefile_abi_test/basic_abi_tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use std::borrow::Cow; 3 | use std::cell::{Cell, UnsafeCell}; 4 | use std::io::Cursor; 5 | 6 | #[cfg(feature = "nightly")] 7 | use test::Bencher; 8 | 9 | use savefile::{AbiRemoved, Deserializer, Removed, Serialize, Serializer, ValueConstructor}; 10 | use savefile_abi::{abi_entry, AbiConnection, AbiExportable, AbiExportableImplementation}; 11 | 12 | #[savefile_abi_exportable(version = 0)] 13 | pub trait CallbackInterface { 14 | fn set(&mut self, x: u32); 15 | fn get(&self) -> u32; 16 | } 17 | 18 | #[derive(Savefile)] 19 | pub struct SomeRandomType; 20 | 21 | #[savefile_abi_exportable(version = 0)] 22 | pub trait TestInterface { 23 | fn add(&self, x: u32, y: String) -> u32; 24 | fn call_callback(&mut self, callback: &mut dyn CallbackInterface); 25 | fn do_nothing(&self); 26 | fn do_panic(&self); 27 | fn arrays_add(&self, a: &[u32], b: &[u32]) -> Vec; 28 | fn string_arrays_add(&self, a: &[String], b: &[String]) -> Vec; 29 | 30 | fn do_mut_nothing(&mut self); 31 | 32 | fn deref_u32(&self, x: &u32) -> u32; 33 | fn count_chars(&self, x: &String) -> usize; 34 | fn count_chars_str(&self, x: &str) -> usize; 35 | 36 | fn zero_sized_arg(&self, zero: ()); 37 | fn simple_add(&self, a: u32, b: u32) -> u32; 38 | 39 | fn tuple_add1(&self, a: (u32,), b: (u32,)) -> (u32,); 40 | fn tuple_add2(&self, a: (u32, u32), b: (u32, u32)) -> (u32, u32); 41 | fn tuple_add3(&self, a: (u32, u32, u32), b: (u32, u32, u32)) -> (u32, u32, u32); 42 | 43 | fn boxes(&self, a: Box) -> Box; 44 | 45 | fn test_default_impl(&self) -> String { 46 | "hello".to_string() 47 | } 48 | 49 | fn get_static_str(&self) -> &'static str; 50 | // Test using lots of symbol-names from the derive-macro, to verify 51 | // there's no crashes 52 | fn test_macro_hygiene( 53 | &self, 54 | context: SomeRandomType, 55 | schema: SomeRandomType, 56 | trait_object: SomeRandomType, 57 | get_schema: SomeRandomType, 58 | method_number: SomeRandomType, 59 | effective_version: SomeRandomType, 60 | new: SomeRandomType, 61 | result_buffer: SomeRandomType, 62 | compatibility_mask: SomeRandomType, 63 | callee_method_number: SomeRandomType, 64 | info: SomeRandomType, 65 | serializer: SomeRandomType, 66 | outcome: SomeRandomType, 67 | result_receiver: SomeRandomType, 68 | abi_result_receiver: SomeRandomType, 69 | resval: SomeRandomType, 70 | abi_result: SomeRandomType, 71 | err_str: SomeRandomType, 72 | ret: SomeRandomType, 73 | cursor: SomeRandomType, 74 | deserializer: SomeRandomType, 75 | ) { 76 | } 77 | } 78 | 79 | #[derive(Default)] 80 | pub struct TestInterfaceImpl {} 81 | 82 | pub struct CallbackImpl { 83 | x: u32, 84 | } 85 | 86 | impl CallbackInterface for CallbackImpl { 87 | fn set(&mut self, x: u32) { 88 | self.x = x; 89 | } 90 | 91 | fn get(&self) -> u32 { 92 | self.x 93 | } 94 | } 95 | 96 | impl TestInterface for TestInterfaceImpl { 97 | fn add(&self, x: u32, y: String) -> u32 { 98 | x + y.parse::().unwrap() 99 | } 100 | 101 | fn call_callback(&mut self, callback: &mut dyn CallbackInterface) { 102 | callback.set(42); 103 | } 104 | fn arrays_add(&self, a: &[u32], b: &[u32]) -> Vec { 105 | let mut ret = Vec::new(); 106 | for (a0, b0) in a.iter().copied().zip(b.iter().copied()) { 107 | ret.push(a0 + b0); 108 | } 109 | 110 | ret 111 | } 112 | fn do_nothing(&self) {} 113 | fn do_panic(&self) { 114 | panic!("TestInterface was asked to panic") 115 | } 116 | fn do_mut_nothing(&mut self) {} 117 | 118 | fn zero_sized_arg(&self, _zero: ()) {} 119 | 120 | fn simple_add(&self, a: u32, b: u32) -> u32 { 121 | a + b 122 | } 123 | 124 | fn tuple_add1(&self, a: (u32,), b: (u32,)) -> (u32,) { 125 | (a.0 + b.0,) 126 | } 127 | 128 | fn tuple_add2(&self, a: (u32, u32), b: (u32, u32)) -> (u32, u32) { 129 | (a.0 + b.0, a.1 + b.1) 130 | } 131 | 132 | fn tuple_add3(&self, a: (u32, u32, u32), b: (u32, u32, u32)) -> (u32, u32, u32) { 133 | (a.0 + b.0, a.1 + b.1, a.2 + b.2) 134 | } 135 | 136 | fn boxes(&self, a: Box) -> Box { 137 | a 138 | } 139 | 140 | fn string_arrays_add(&self, a: &[String], b: &[String]) -> Vec { 141 | let mut ret = vec![]; 142 | for (a1, b1) in a.iter().zip(b.iter()) { 143 | ret.push(a1.to_string() + b1); 144 | } 145 | ret 146 | } 147 | 148 | fn count_chars(&self, x: &String) -> usize { 149 | x.len() 150 | } 151 | fn count_chars_str(&self, x: &str) -> usize { 152 | x.len() 153 | } 154 | 155 | fn get_static_str(&self) -> &'static str { 156 | "hello world" 157 | } 158 | 159 | fn deref_u32(&self, x: &u32) -> u32 { 160 | *x 161 | } 162 | } 163 | 164 | savefile_abi_export!(TestInterfaceImpl, TestInterface); 165 | 166 | #[test] 167 | fn test_basic_call_abi() { 168 | let boxed: Box = Box::new(TestInterfaceImpl {}); 169 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 170 | 171 | let mut callback = CallbackImpl { x: 43 }; 172 | conn.call_callback(&mut callback); 173 | 174 | assert_eq!(callback.x, 42); 175 | 176 | assert_eq!(conn.tuple_add1((1,), (2,)), (3,)); 177 | assert_eq!(conn.tuple_add2((1, 1), (2, 2)), (3, 3)); 178 | assert_eq!(conn.tuple_add3((1, 1, 1), (2, 2, 2)), (3, 3, 3)); 179 | assert_eq!(conn.boxes(Box::new(42u32)), Box::new(42u32)); 180 | assert_eq!(conn.test_default_impl(), "hello"); 181 | 182 | assert_eq!(conn.count_chars(&"hejsan".to_string()), 6); 183 | assert_eq!(conn.count_chars_str("hejsan"), 6); 184 | assert!(conn.get_arg_passable_by_ref("count_chars", 0)); 185 | assert_eq!(conn.get_static_str(), "hello world"); 186 | 187 | assert_eq!(conn.deref_u32(&42), 42); 188 | assert!(conn.get_arg_passable_by_ref("deref_u32", 0)); 189 | } 190 | 191 | #[test] 192 | fn test_slices() { 193 | let boxed: Box = Box::new(TestInterfaceImpl {}); 194 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 195 | 196 | let t = conn.arrays_add(&[1, 2, 3], &[1, 2, 3]); 197 | assert_eq!(t, vec![2, 4, 6]); 198 | 199 | let t = conn.string_arrays_add(&["hello ".to_string()], &["world".to_string()]); 200 | assert_eq!(t, vec!["hello world"]); 201 | } 202 | 203 | #[test] 204 | fn test_zero_sized_arg() { 205 | let boxed: Box = Box::new(TestInterfaceImpl {}); 206 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 207 | conn.zero_sized_arg(()); 208 | } 209 | #[test] 210 | #[should_panic(expected = "TestInterface was asked to panic")] 211 | fn test_panicking() { 212 | let boxed: Box = Box::new(TestInterfaceImpl {}); 213 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 214 | conn.do_panic(); 215 | } 216 | 217 | #[test] 218 | fn test_big_slices() { 219 | let boxed: Box = Box::new(TestInterfaceImpl {}); 220 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 221 | let a = vec![1u32; 10000]; 222 | let b = vec![1u32; 10000]; 223 | 224 | let t = conn.arrays_add(&a, &b); 225 | assert_eq!(t.len(), 10000); 226 | for x in t { 227 | assert_eq!(x, 2); 228 | } 229 | } 230 | 231 | struct FortyTwoConstructor {} 232 | impl ValueConstructor for FortyTwoConstructor { 233 | fn make_value() -> u32 { 234 | 42 235 | } 236 | } 237 | 238 | #[test] 239 | fn test_abi_removed() { 240 | let removed: AbiRemoved = AbiRemoved::new(); 241 | let mut data = Vec::new(); 242 | Serializer::bare_serialize(&mut data, 0, &removed).unwrap(); 243 | 244 | let roundtripped: u32 = Deserializer::bare_deserialize(&mut Cursor::new(&data), 0).unwrap(); 245 | assert_eq!(roundtripped, 0); 246 | } 247 | #[test] 248 | fn test_abi_removed_with_custom_default() { 249 | let removed: AbiRemoved = AbiRemoved::::new(); 250 | let mut data = Vec::new(); 251 | Serializer::bare_serialize(&mut data, 0, &removed).unwrap(); 252 | 253 | let roundtripped: u32 = Deserializer::bare_deserialize(&mut Cursor::new(&data), 0).unwrap(); 254 | assert_eq!(roundtripped, 42); 255 | } 256 | 257 | #[cfg(feature = "nightly")] 258 | #[cfg(not(miri))] 259 | #[bench] 260 | fn bench_simple_call(b: &mut Bencher) { 261 | let boxed: Box = Box::new(TestInterfaceImpl {}); 262 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 263 | 264 | b.iter(move || conn.do_nothing()) 265 | } 266 | 267 | #[cfg(feature = "nightly")] 268 | #[cfg(not(miri))] 269 | #[bench] 270 | fn bench_simple_add(b: &mut Bencher) { 271 | let boxed: Box = Box::new(TestInterfaceImpl {}); 272 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 273 | 274 | b.iter(move || conn.simple_add(std::hint::black_box(1), std::hint::black_box(2))) 275 | } 276 | #[cfg(feature = "nightly")] 277 | #[cfg(not(miri))] 278 | #[bench] 279 | fn bench_count_chars(b: &mut Bencher) { 280 | let boxed: Box = Box::new(TestInterfaceImpl {}); 281 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 282 | let mut s = String::new(); 283 | use std::fmt::Write; 284 | for i in 0..10000 { 285 | write!(s, "{}", i).unwrap(); 286 | } 287 | b.iter(move || conn.count_chars(std::hint::black_box(&s))) 288 | } 289 | #[cfg(feature = "nightly")] 290 | #[cfg(not(miri))] 291 | #[bench] 292 | fn bench_count_chars_str(b: &mut Bencher) { 293 | let boxed: Box = Box::new(TestInterfaceImpl {}); 294 | let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 295 | let mut s = String::new(); 296 | use std::fmt::Write; 297 | for i in 0..10000 { 298 | write!(s, "{}", i).unwrap(); 299 | } 300 | b.iter(move || conn.count_chars_str(std::hint::black_box(&s))) 301 | } 302 | 303 | #[savefile_abi_exportable(version = 0)] 304 | pub trait CowSmuggler { 305 | // Specifying &'static is supported. Otherwise, the lifetime 306 | // becomes artificially short in this case (it becomes that of &self). 307 | fn smuggle2(&mut self, x: Cow) -> Cow<'static, str>; 308 | // In this case, the lifetime of Cow is that of &mut self. 309 | // (Rust lifetime elision rules). 310 | fn smuggle(&mut self, x: Cow) -> Cow<'_, str>; 311 | } 312 | impl CowSmuggler for () { 313 | fn smuggle(&mut self, x: Cow) -> Cow<'_, str> { 314 | (*x).to_owned().into() 315 | } 316 | fn smuggle2(&mut self, x: Cow) -> Cow<'static, str> { 317 | (*x).to_owned().into() 318 | } 319 | } 320 | 321 | #[test] 322 | fn test_cow_smuggler() { 323 | let boxed: Box = Box::new(()); 324 | let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); 325 | assert_eq!(conn.smuggle("hej".into()), "hej"); 326 | assert_eq!(conn.smuggle("hej".to_string().into()), "hej"); 327 | 328 | assert_eq!(conn.smuggle2("hej".into()), "hej"); 329 | assert_eq!(conn.smuggle2("hej".to_string().into()), "hej"); 330 | 331 | let static_ret: Cow<'static, str> = conn.smuggle2("hej".into()); 332 | assert_eq!(static_ret, "hej"); 333 | } 334 | -------------------------------------------------------------------------------- /savefile-test/src/snapshots/savefile_test__test_recursive_types__get_recursive_schema.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: savefile-test/src/lib.rs 3 | expression: schema 4 | --- 5 | Struct( 6 | SchemaStruct { 7 | dbg_name: "RecursiveType", 8 | size: Some( 9 | 40, 10 | ), 11 | alignment: Some( 12 | 8, 13 | ), 14 | fields: [ 15 | Field { 16 | name: "left", 17 | value: SchemaOption( 18 | Struct( 19 | SchemaStruct { 20 | dbg_name: "RecursiveType", 21 | size: Some( 22 | 40, 23 | ), 24 | alignment: Some( 25 | 8, 26 | ), 27 | fields: [ 28 | Field { 29 | name: "left", 30 | value: SchemaOption( 31 | Recursion( 32 | 1, 33 | ), 34 | ), 35 | offset: Some( 36 | 24, 37 | ), 38 | }, 39 | Field { 40 | name: "right", 41 | value: SchemaOption( 42 | Recursion( 43 | 1, 44 | ), 45 | ), 46 | offset: Some( 47 | 32, 48 | ), 49 | }, 50 | Field { 51 | name: "mid", 52 | value: Vector( 53 | Struct( 54 | SchemaStruct { 55 | dbg_name: "Relay", 56 | size: Some( 57 | 8, 58 | ), 59 | alignment: Some( 60 | 8, 61 | ), 62 | fields: [ 63 | Field { 64 | name: "relay", 65 | value: Recursion( 66 | 2, 67 | ), 68 | offset: Some( 69 | 0, 70 | ), 71 | }, 72 | ], 73 | }, 74 | ), 75 | CapacityDataLength, 76 | ), 77 | offset: Some( 78 | 0, 79 | ), 80 | }, 81 | ], 82 | }, 83 | ), 84 | ), 85 | offset: Some( 86 | 24, 87 | ), 88 | }, 89 | Field { 90 | name: "right", 91 | value: SchemaOption( 92 | Struct( 93 | SchemaStruct { 94 | dbg_name: "RecursiveType", 95 | size: Some( 96 | 40, 97 | ), 98 | alignment: Some( 99 | 8, 100 | ), 101 | fields: [ 102 | Field { 103 | name: "left", 104 | value: SchemaOption( 105 | Recursion( 106 | 1, 107 | ), 108 | ), 109 | offset: Some( 110 | 24, 111 | ), 112 | }, 113 | Field { 114 | name: "right", 115 | value: SchemaOption( 116 | Recursion( 117 | 1, 118 | ), 119 | ), 120 | offset: Some( 121 | 32, 122 | ), 123 | }, 124 | Field { 125 | name: "mid", 126 | value: Vector( 127 | Struct( 128 | SchemaStruct { 129 | dbg_name: "Relay", 130 | size: Some( 131 | 8, 132 | ), 133 | alignment: Some( 134 | 8, 135 | ), 136 | fields: [ 137 | Field { 138 | name: "relay", 139 | value: Recursion( 140 | 2, 141 | ), 142 | offset: Some( 143 | 0, 144 | ), 145 | }, 146 | ], 147 | }, 148 | ), 149 | CapacityDataLength, 150 | ), 151 | offset: Some( 152 | 0, 153 | ), 154 | }, 155 | ], 156 | }, 157 | ), 158 | ), 159 | offset: Some( 160 | 32, 161 | ), 162 | }, 163 | Field { 164 | name: "mid", 165 | value: Vector( 166 | Struct( 167 | SchemaStruct { 168 | dbg_name: "Relay", 169 | size: Some( 170 | 8, 171 | ), 172 | alignment: Some( 173 | 8, 174 | ), 175 | fields: [ 176 | Field { 177 | name: "relay", 178 | value: Struct( 179 | SchemaStruct { 180 | dbg_name: "RecursiveType", 181 | size: Some( 182 | 40, 183 | ), 184 | alignment: Some( 185 | 8, 186 | ), 187 | fields: [ 188 | Field { 189 | name: "left", 190 | value: SchemaOption( 191 | Recursion( 192 | 1, 193 | ), 194 | ), 195 | offset: Some( 196 | 24, 197 | ), 198 | }, 199 | Field { 200 | name: "right", 201 | value: SchemaOption( 202 | Recursion( 203 | 1, 204 | ), 205 | ), 206 | offset: Some( 207 | 32, 208 | ), 209 | }, 210 | Field { 211 | name: "mid", 212 | value: Vector( 213 | Recursion( 214 | 2, 215 | ), 216 | CapacityDataLength, 217 | ), 218 | offset: Some( 219 | 0, 220 | ), 221 | }, 222 | ], 223 | }, 224 | ), 225 | offset: Some( 226 | 0, 227 | ), 228 | }, 229 | ], 230 | }, 231 | ), 232 | CapacityDataLength, 233 | ), 234 | offset: Some( 235 | 0, 236 | ), 237 | }, 238 | ], 239 | }, 240 | ) 241 | -------------------------------------------------------------------------------- /savefile-abi/async.md: -------------------------------------------------------------------------------- 1 | # Async support in Savefile-abi 2 | 3 | [Savefile-abi](https://crates.io/crates/savefile-abi) is a rust crate that allows exposing traits with a 4 | stable ABI from shared libraries in rust. 5 | 6 | Savefile-abi recently gained support for async methods. 7 | 8 | This means that you can now define this trait, for example: 9 | 10 | 11 | ```rust 12 | 13 | 14 | #[async_trait] 15 | #[savefile_abi_exportable(version = 0)] 16 | pub trait MyDatabaseInterface { 17 | 18 | async fn get(&self, key: String) -> String; 19 | async fn set(&mut self, key: String, value: String); 20 | } 21 | 22 | 23 | ``` 24 | 25 | You can then create an implementation, and compile it to a freestanding shared library, as a plugin: 26 | 27 | ```rust 28 | 29 | struct MyDatabaseImpl { 30 | // impl 31 | } 32 | 33 | #[async_trait] 34 | impl MyDatabaseInterface for MyDatabaseImpl { 35 | async fn get(&self, key: String) -> String { 36 | // call, and await, async methods here 37 | "placeholder".to_string() 38 | } 39 | async fn set(&mut self, key: String, value: String) { 40 | // implementation 41 | } 42 | 43 | } 44 | 45 | savefile_abi_export!(MyDatabaseInterface, MyDatabaseImpl); 46 | 47 | ``` 48 | 49 | It is possible to have many such plugins, implementing a particular interface (such as `MyDatabaseInterface` here), 50 | and load these plugins at runtime, without requiring all the plugins to be compiled with the same rust-version 51 | as the main application. 52 | 53 | 54 | ## Background, what is async good for 55 | 56 | (Skip this section if you're already familiar with async) 57 | 58 | There are many cases where one might want to use async. Let's look at an example. 59 | 60 | Let's say we have a program that calculates the weight of a set of goods, using some logic: 61 | 62 | ```rust 63 | 64 | fn calculate_weight(items: &[Item]) -> u32 { 65 | 66 | let container = get_suitable_container(items.len()); 67 | 68 | let mut sum = container.weight; 69 | for item in items { 70 | sum += get_item_weight(item); 71 | } 72 | 73 | sum 74 | } 75 | ``` 76 | 77 | Now, let's say our program needs to do hundreds of thousands of these calculations every second, and let's assume 78 | that the two get_-functions need to do database access and actually have some significant latency. Maybe each execution 79 | of 'calculate_weight' takes 1 second because of this. To process 100000 items per second, we'd then need 100000 threads. 80 | 81 | However, the cost of spawning 100000 threads is significant. Instead, we can use async: 82 | 83 | ```rust 84 | 85 | async fn calculate_weight(items: &[Item]) -> u32 { 86 | 87 | let container = get_suitable_container(items.len()).await; 88 | 89 | let mut sum = container.weight; 90 | for item in items { 91 | sum += get_item_weight(item).await; 92 | } 93 | 94 | sum 95 | } 96 | ``` 97 | 98 | Now, using an async runtime such as [tokio](https://tokio.rs/) , many 'calculate_weight'-calculations can 99 | occur simultaneously on the same thread. Under the hood, the above method behaves much as if it was implemented 100 | like this: 101 | 102 | ```rust 103 | fn calculate_weight(items: &[Item]) -> impl Future { 104 | async { 105 | let container = get_suitable_container(items.len()).await; 106 | 107 | let mut sum = container.weight; 108 | for item in items { 109 | sum += get_item_weight(item).await; 110 | } 111 | } 112 | sum 113 | } 114 | ``` 115 | 116 | The async keyword constructs a future based on the code block. A future is an object that can be polled, that will 117 | eventually produce a value. Let's look at how the future trait is defined: 118 | 119 | ```rust 120 | pub trait Future { 121 | type Output; 122 | 123 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; 124 | } 125 | ``` 126 | An async-block such as the one in `calculate_weight` above, implements this trait. 127 | 128 | The idea is that the code within the async-block in 'calculate_weight' is converted, by the rust compiler, into a 129 | state machine, that will first call `get_suitable_container` to obtain a future, poll that future until it gets the 130 | container, then enter the loop and do the same with each future produced by `get_item_weight`. Note that this state 131 | machine could be written manually, but it would be much less ergonomic than using an `async` code block. 132 | 133 | Async blocks are allowed to contain references to variables inside the async block. This means that the 134 | future cannot be moved, a fact which is represented using the `Pin<&mut Self>` type for the self-argument to `poll`. 135 | 136 | There are of course many more details. See the [Async book](https://rust-lang.github.io/async-book/01_getting_started/04_async_await_primer.html) 137 | for more information. 138 | 139 | ## Implementing async support in Savefile-abi 140 | 141 | My first gut reaction was that this was going to be very hard, borderline impossible, and difficult to get right. 142 | As it turns out, it was actually quite easy, and not even that scary. This blog post describes the effort of adding 143 | async-support to Savefile-abi. 144 | 145 | One thing that needs to be said immediately, up front, is that Savefile-abi only supports a subset of all possible 146 | trait objects. This is true for savefile abi in general, not only for the async-support. In principle, Savefile-abi is 147 | a bit of a "hack" (though (hopefully), a useful one). Some effort has been made to give detailed and developer-friendly 148 | error messages whenever one of the limitations are hit. 149 | 150 | One of the biggest limitations for async-support is that Savefile-abi does not support references for arguments 151 | in async methods. All arguments have to be owned. More about this further down. 152 | 153 | The basic way that Savefile-abi works is that implementations of the trait `AbiExportable` are generated for each 154 | trait that is to be exported across shared library boundaries. This trait exposes methods to return the signatures 155 | of all methods in the exported trait, as well as a extern "C"-function that allows calling trait methods. The actual data 156 | is sent as plain bytes in a savefile-abi proprietary binary format, in a forward- and backward-compatible manner. 157 | 158 | Savefile-abi already supports boxed dyn trait objects in return position. So the fundamental implementation strategy for 159 | supporting async is to make it possible for Savefile-abi to support returning `Box>`. One approach 160 | might be for the savefile-abi crate to simply implement `AbiExportable` for `Box>`, for all T. 161 | However, this runs into problems, because the underlying savefile-abi mechanism does not support generics. It is also 162 | not obvious what the trait bounds for T would be. 163 | 164 | Instead, the chosen mechanism is to make the `savefile-derive` crate generate new traits, with a Savefile-abi 165 | compatible signature, and expose these over the savefile abi instead of the standard Future trait. Here's an example of 166 | such a wrapper for futures producing values of type u32: 167 | 168 | ```rust 169 | pub trait FutureWrapper { 170 | fn abi_poll(self: Pin<&mut Self>, waker: Box) -> Option; 171 | } 172 | 173 | ``` 174 | 175 | The above wrapper is then implemented (using a unique private name) for `Pin>>`. A custom implementation 176 | of [Wake](https://doc.rust-lang.org/std/task/trait.Wake.html) is created, and used to create a 177 | [Waker](https://doc.rust-lang.org/std/task/struct.Waker.html) and 178 | [Context](https://doc.rust-lang.org/std/task/struct.Context.html) that can be passed to the `poll` method of `Future`. 179 | This custom waker ten simply calls the Boxed dyn Fn `waker` of `abi_poll`. 180 | 181 | The challenge here is that Savefile-abi did not previously support `Pin<&mut Self>` for self. Adding this 182 | support was relatively straightforward, since, under the hood, `Pin<&mut Self>` is still just a regular self-pointer. 183 | 184 | The next challenge is lifetimes. Savefile abi can never support lifetimes in return position. The reason for this 185 | is that savefile falls back on serialization, if memory layout of data types changes between different 186 | versions of an interface. This means that only owned values can be supported, since it's impossible to create 187 | deserialized values with arbitrary lifetimes. 188 | 189 | However, futures can normally capture argument lifetimes, especially that of 'self'. Supporting reference arguments 190 | in functions returning futures turns out to be hard, since the references may have to be serialized before 191 | sending to the callee, which means that the return value can't be allowed capture them. However, we can support 192 | futures that capture 'self', since self is never serialized in Savefile-abi. Expressing that a future 193 | captures 'self' is often done using lifetime-annotations, something Savefile-abi previously did not support. 194 | 195 | That said, this was always an arbitrary and unnecessary limitation. Even if `self` is written as just `&self`, 196 | it still has a lifetime, even if that lifetime isn't given a name. There's no reason why Savefile shouldn't 197 | support a method like this: 198 | 199 | ```rust 200 | fn some_function<'a>(&'a self) -> u32; 201 | ``` 202 | 203 | Previously, there was also no reason _why_ such a method should be supported, since the annotation 204 | doesn't add anything. However, users of savefile abi will probably want to use the `#[async_trait]`-macro 205 | (see [async-trait](https://docs.rs/async-trait/latest/async_trait/)). 206 | 207 | This macro does add lifetime annotations, which can look something like this: 208 | 209 | ```rust 210 | fn set<'life0, 'async_trait>( 211 | &'life0 self, 212 | value: u32, 213 | ) -> ::core::pin::Pin + ::core::marker::Send + 'async_trait>> 214 | where 215 | 'life0: 'async_trait, 216 | Self: 'async_trait; 217 | ``` 218 | The above syntax describes that the returned future is (potentially) capturing `self`. 219 | 220 | To be able to use Savefile abi with the 'async_trait'-macro, the above must be supported. However, most usages 221 | of lifetimes in Savefile abi don't make sense, since savefile can always fall back on serialization. 222 | 223 | As of version 0.18.2, savefile-abi uses heuristics to detect usage of `#[async_trait]`, and allows lifetime annotations 224 | _only_ if they appear to follow the pattern used by async_trait. This limitation could be relaxed. The main 225 | challenge is making sure that all the code that savefile-derive generates is compatible with the lifetime annotations, 226 | and also making sure that adding such annotations can't be used to somehow cause unsoundness. Basically, as long 227 | as we don't allow references in return position, nothing that can be expressed using lifetime annotations or where-clauses 228 | should cause a problem, so this is definitely something to do in future versions of savefile-abi. 229 | 230 | 231 | ## Performance 232 | 233 | As described above, savefile-abi relies on boxed futures. This means that each call to an async-function requires 234 | memory allocation. Savefile-abi is thus definitely not free. A simple benchmark of a minimal async-invocation, 235 | gives an overhead of ca 200 ns (on an AMD 7950X CPU). This is much, much more expensive than a regular function 236 | call, but typically cheaper than a gRPC-call or other IPC. 237 | 238 | It is probably possible to optimize the performance at least slightly, by trying to reduce the need for memory 239 | allocation. Right now the Waker requires several memory allocations, something that could conceivably be alleviated. 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /savefile-derive/src/deserialize.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{check_is_remove, get_extra_where_clauses, parse_attr_tag, FieldInfo, RemovedType}; 2 | use crate::{doc_hidden, get_enum_size}; 3 | use proc_macro2::{Literal, TokenStream}; 4 | use syn::spanned::Spanned; 5 | use syn::DeriveInput; 6 | 7 | fn implement_deserialize(field_infos: Vec) -> Vec { 8 | let span = proc_macro2::Span::call_site(); 9 | let defspan = proc_macro2::Span::call_site(); 10 | let removeddef = quote_spanned! { defspan => _savefile::prelude::Removed }; 11 | let abiremoveddef = quote_spanned! { defspan => _savefile::prelude::AbiRemoved }; 12 | let local_deserializer = quote_spanned! { defspan => deserializer}; 13 | 14 | let mut output = Vec::new(); 15 | //let mut min_safe_version = 0; 16 | for field in &field_infos { 17 | let field_type = &field.ty; 18 | 19 | let is_removed = check_is_remove(field_type); 20 | 21 | let verinfo = parse_attr_tag(field.attrs); 22 | let (field_from_version, field_to_version, default_fn, default_val) = ( 23 | verinfo.version_from, 24 | verinfo.version_to, 25 | verinfo.default_fn, 26 | verinfo.default_val, 27 | ); 28 | let mut exists_version_which_needs_default_value = false; 29 | if verinfo.ignore { 30 | exists_version_which_needs_default_value = true; 31 | } else { 32 | for ver in 0..verinfo.version_from { 33 | if !verinfo.deserialize_types.iter().any(|x| ver >= x.from && ver <= x.to) { 34 | exists_version_which_needs_default_value = true; 35 | } 36 | } 37 | } 38 | 39 | let effective_default_val = if is_removed.is_removed() { 40 | match is_removed { 41 | RemovedType::Removed => quote! { #removeddef::new() }, 42 | RemovedType::AbiRemoved => quote! { #abiremoveddef::new() }, 43 | _ => unreachable!(), 44 | } 45 | } else if let Some(defval) = default_val { 46 | quote! { #defval } 47 | } else if let Some(default_fn) = default_fn { 48 | quote_spanned! { span => #default_fn() } 49 | } else if !exists_version_which_needs_default_value { 50 | quote! { panic!("Unexpected unsupported file version: {}",#local_deserializer.file_version) } 51 | //Should be impossible 52 | } else { 53 | quote_spanned! { span => Default::default() } 54 | }; 55 | if field_from_version > field_to_version { 56 | abort!( 57 | field.field_span, 58 | "Version range is reversed. This is not allowed. Version must be range like 0..2, not like 2..0" 59 | ); 60 | } 61 | 62 | let src = if field_from_version == 0 && field_to_version == std::u32::MAX && !verinfo.ignore { 63 | if is_removed.is_removed() { 64 | abort!( 65 | field_type.span(), 66 | "The Removed type may only be used for fields which have an old version." 67 | ); 68 | //TODO: Better message, tell user how to do this annotation 69 | }; 70 | quote_spanned! { span => 71 | <#field_type as _savefile::prelude::Deserialize>::deserialize(#local_deserializer)? 72 | } 73 | } else if verinfo.ignore { 74 | quote_spanned! { span => 75 | #effective_default_val 76 | } 77 | } else { 78 | //min_safe_version = min_safe_version.max(verinfo.min_safe_version()); 79 | let mut version_mappings = Vec::new(); 80 | for dt in verinfo.deserialize_types.iter() { 81 | let dt_from = dt.from; 82 | let dt_to = dt.to; 83 | let dt_field_type = syn::Ident::new(&dt.serialized_type, span); 84 | let dt_convert_fun = if dt.convert_fun.len() > 0 { 85 | let dt_conv_fun = syn::Ident::new(&dt.convert_fun, span); 86 | quote! { #dt_conv_fun } 87 | } else { 88 | quote! { <#field_type>::from } 89 | }; 90 | 91 | version_mappings.push(quote! { 92 | if #local_deserializer.file_version >= #dt_from && #local_deserializer.file_version <= #dt_to { 93 | let temp : #dt_field_type = <#dt_field_type as _savefile::prelude::Deserialize>::deserialize(#local_deserializer)?; 94 | #dt_convert_fun(temp) 95 | } else 96 | }); 97 | } 98 | 99 | quote_spanned! { span => 100 | #(#version_mappings)* 101 | if #local_deserializer.file_version >= #field_from_version && #local_deserializer.file_version <= #field_to_version { 102 | <#field_type as _savefile::prelude::Deserialize>::deserialize(#local_deserializer)? 103 | } else { 104 | #effective_default_val 105 | } 106 | } 107 | }; 108 | 109 | if let Some(ref id) = field.ident { 110 | let id_spanned = quote_spanned! { span => #id}; 111 | output.push(quote!(#id_spanned : #src )); 112 | } else { 113 | output.push(quote!( #src )); 114 | } 115 | } 116 | output 117 | } 118 | 119 | pub fn savefile_derive_crate_deserialize(input: DeriveInput) -> TokenStream { 120 | let span = proc_macro2::Span::call_site(); 121 | let defspan = proc_macro2::Span::call_site(); 122 | 123 | let name = &input.ident; 124 | 125 | let doc_hidden = doc_hidden(&input.attrs); 126 | let generics = &input.generics; 127 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 128 | let extra_where = get_extra_where_clauses( 129 | &input, 130 | where_clause, 131 | quote! {_savefile::prelude::Deserialize + _savefile::prelude::Packed}, 132 | ); 133 | 134 | let deserialize = quote_spanned! {defspan=> 135 | _savefile::prelude::Deserialize 136 | }; 137 | 138 | let uses = quote_spanned! { defspan => 139 | extern crate savefile as _savefile; 140 | }; 141 | 142 | let deserializer = quote_spanned! {defspan=> 143 | _savefile::prelude::Deserializer 144 | }; 145 | 146 | let saveerr = quote_spanned! {defspan=> 147 | _savefile::prelude::SavefileError 148 | }; 149 | 150 | let dummy_const = syn::Ident::new("_", proc_macro2::Span::call_site()); 151 | 152 | let expanded = match &input.data { 153 | &syn::Data::Enum(ref enum1) => { 154 | let mut output = Vec::new(); 155 | //let variant_count = enum1.variants.len(); 156 | let enum_size = get_enum_size(&input.attrs, enum1.variants.len()); 157 | 158 | for (var_idx_usize, variant) in enum1.variants.iter().enumerate() { 159 | let var_idx = Literal::u32_unsuffixed(var_idx_usize as u32); 160 | 161 | let var_ident = variant.ident.clone(); 162 | let variant_name = quote! { #name::#var_ident }; 163 | let variant_name_spanned = quote_spanned! { span => #variant_name}; 164 | match &variant.fields { 165 | &syn::Fields::Named(ref fields_named) => { 166 | let field_infos: Vec = fields_named 167 | .named 168 | .iter() 169 | .enumerate() 170 | .map(|(field_index, field)| FieldInfo { 171 | ident: Some(field.ident.clone().expect("Expected identifier [6]")), 172 | field_span: field.ident.as_ref().unwrap().span(), 173 | ty: &field.ty, 174 | index: field_index as u32, 175 | attrs: &field.attrs, 176 | }) 177 | .collect(); 178 | 179 | let fields_deserialized = implement_deserialize(field_infos); 180 | 181 | output.push(quote!( #var_idx => #variant_name_spanned{ #(#fields_deserialized,)* } )); 182 | } 183 | &syn::Fields::Unnamed(ref fields_unnamed) => { 184 | let field_infos: Vec = fields_unnamed 185 | .unnamed 186 | .iter() 187 | .enumerate() 188 | .map(|(field_index, field)| FieldInfo { 189 | ident: None, 190 | field_span: field.ty.span(), 191 | ty: &field.ty, 192 | index: field_index as u32, 193 | attrs: &field.attrs, 194 | }) 195 | .collect(); 196 | let fields_deserialized = implement_deserialize(field_infos); 197 | 198 | output.push(quote!( #var_idx => #variant_name_spanned( #(#fields_deserialized,)*) )); 199 | } 200 | &syn::Fields::Unit => { 201 | output.push(quote!( #var_idx => #variant_name_spanned )); 202 | } 203 | } 204 | } 205 | 206 | let variant_deserializer = match enum_size.discriminant_size { 207 | 1 => quote! { deserializer.read_u8()? }, 208 | 2 => quote! { deserializer.read_u16()? }, 209 | 4 => quote! { deserializer.read_u32()? }, 210 | _ => unreachable!(), 211 | }; 212 | 213 | quote! { 214 | #[allow(non_upper_case_globals)] 215 | #[allow(clippy::double_comparisons)] 216 | #[allow(clippy::manual_range_contains)] 217 | const #dummy_const: () = { 218 | #uses 219 | #[automatically_derived] 220 | #doc_hidden 221 | impl #impl_generics #deserialize for #name #ty_generics #where_clause #extra_where { 222 | #[allow(unused_comparisons, unused_variables)] 223 | fn deserialize(deserializer: &mut #deserializer) -> Result { 224 | 225 | Ok(match #variant_deserializer { 226 | #(#output,)* 227 | _ => return Err(_savefile::prelude::SavefileError::GeneralError{msg:format!("Corrupt file - unknown enum variant detected.")}) 228 | }) 229 | } 230 | } 231 | }; 232 | } 233 | } 234 | &syn::Data::Struct(ref struc) => { 235 | let output = match &struc.fields { 236 | &syn::Fields::Named(ref namedfields) => { 237 | let field_infos: Vec = namedfields 238 | .named 239 | .iter() 240 | .enumerate() 241 | .map(|(field_index, field)| FieldInfo { 242 | ident: Some(field.ident.clone().expect("Expected identifier[7]")), 243 | field_span: field.ident.as_ref().unwrap().span(), 244 | index: field_index as u32, 245 | ty: &field.ty, 246 | attrs: &field.attrs, 247 | }) 248 | .collect(); 249 | 250 | let output1 = implement_deserialize(field_infos); 251 | quote! {Ok(#name { 252 | #(#output1,)* 253 | })} 254 | } 255 | &syn::Fields::Unnamed(ref fields_unnamed) => { 256 | let field_infos: Vec = fields_unnamed 257 | .unnamed 258 | .iter() 259 | .enumerate() 260 | .map(|(field_index, field)| FieldInfo { 261 | ident: None, 262 | field_span: field.ty.span(), 263 | index: field_index as u32, 264 | ty: &field.ty, 265 | attrs: &field.attrs, 266 | }) 267 | .collect(); 268 | let output1 = implement_deserialize(field_infos); 269 | 270 | quote! {Ok(#name ( 271 | #(#output1,)* 272 | ))} 273 | } 274 | &syn::Fields::Unit => { 275 | quote! {Ok(#name )} 276 | } //_ => panic!("Only regular structs supported, not tuple structs."), 277 | }; 278 | quote! { 279 | #[allow(non_upper_case_globals)] 280 | #[allow(clippy::double_comparisons)] 281 | #[allow(clippy::manual_range_contains)] 282 | const #dummy_const: () = { 283 | #uses 284 | #[automatically_derived] 285 | #doc_hidden 286 | impl #impl_generics #deserialize for #name #ty_generics #where_clause #extra_where { 287 | #[allow(unused_comparisons, unused_variables)] 288 | fn deserialize(deserializer: &mut #deserializer) -> Result { 289 | #output 290 | } 291 | } 292 | }; 293 | } 294 | } 295 | _ => { 296 | abort_call_site!("Only regular structs are supported"); 297 | } 298 | }; 299 | 300 | expanded 301 | } 302 | -------------------------------------------------------------------------------- /savefile-abi/src/bytes.rs: -------------------------------------------------------------------------------- 1 | use bytes::buf::UninitSlice; 2 | use bytes::BufMut; 3 | use core::slice; 4 | 5 | extern crate savefile; 6 | extern crate savefile_derive; 7 | use crate::{ 8 | abi_entry_light, parse_return_value_impl, AbiConnection, AbiConnectionMethod, AbiErrorMsg, AbiExportable, 9 | AbiProtocol, FlexBuffer, RawAbiCallResult, TraitObject, 10 | }; 11 | use savefile::prelude::{ 12 | get_schema, AbiMethod, AbiMethodArgument, AbiMethodInfo, AbiTraitDefinition, Deserialize, Deserializer, 13 | LittleEndian, ReadBytesExt, ReceiverType, SavefileError, Schema, Serialize, Serializer, 14 | }; 15 | use std::collections::HashMap; 16 | use std::io::Cursor; 17 | use std::mem::MaybeUninit; 18 | unsafe extern "C" fn abi_entry_light_buf_mut(flag: AbiProtocol) { 19 | unsafe { 20 | abi_entry_light::(flag); 21 | } 22 | } 23 | 24 | unsafe impl AbiExportable for dyn BufMut { 25 | const ABI_ENTRY: unsafe extern "C" fn(flag: AbiProtocol) = abi_entry_light_buf_mut; 26 | fn get_definition(version: u32) -> AbiTraitDefinition { 27 | AbiTraitDefinition { 28 | name: "BufMut".to_string(), 29 | methods: vec![ 30 | AbiMethod { 31 | name: "remaining_mut".to_string(), 32 | info: AbiMethodInfo { 33 | return_value: { get_schema::(version) }, 34 | receiver: ReceiverType::Shared, 35 | arguments: vec![], 36 | async_trait_heuristic: false, 37 | }, 38 | }, 39 | AbiMethod { 40 | name: "advance_mut".to_string(), 41 | info: AbiMethodInfo { 42 | return_value: { get_schema::<()>(0) }, 43 | receiver: ReceiverType::Mut, 44 | arguments: vec![AbiMethodArgument { 45 | schema: { get_schema::(version) }, 46 | }], 47 | async_trait_heuristic: false, 48 | }, 49 | }, 50 | AbiMethod { 51 | name: "chunk_mut".to_string(), 52 | info: AbiMethodInfo { 53 | return_value: Schema::UninitSlice, 54 | receiver: ReceiverType::Mut, 55 | arguments: vec![], 56 | async_trait_heuristic: false, 57 | }, 58 | }, 59 | ], 60 | sync: false, 61 | send: false, 62 | } 63 | } 64 | fn get_latest_version() -> u32 { 65 | 0u32 66 | } 67 | 68 | // TODO: This method should be marked unsafe! 69 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 70 | fn call( 71 | trait_object: TraitObject, 72 | method_number: u16, 73 | effective_version: u32, 74 | _compatibility_mask: u64, 75 | data: &[u8], 76 | abi_result: *mut (), 77 | __savefile_internal_receiver: unsafe extern "C" fn(outcome: *const RawAbiCallResult, result_receiver: *mut ()), 78 | ) -> Result<(), SavefileError> { 79 | let mut cursor = Cursor::new(data); 80 | let mut deserializer = Deserializer { 81 | file_version: cursor.read_u32::()?, 82 | reader: &mut cursor, 83 | ephemeral_state: HashMap::new(), 84 | }; 85 | match method_number { 86 | 0u16 => { 87 | let ret = unsafe { &*trait_object.as_const_ptr::() }.remaining_mut(); 88 | let mut __savefile_internal_data = FlexBuffer::new(); 89 | let mut serializer = Serializer { 90 | writer: &mut __savefile_internal_data, 91 | file_version: 0u32, 92 | }; 93 | serializer.write_u32(effective_version)?; 94 | match ret.serialize(&mut serializer) { 95 | Ok(()) => { 96 | let outcome = RawAbiCallResult::Success { 97 | data: __savefile_internal_data.as_ptr() as *const u8, 98 | len: __savefile_internal_data.len(), 99 | }; 100 | unsafe { __savefile_internal_receiver(&outcome as *const _, abi_result) } 101 | } 102 | Err(err) => { 103 | let err_str = format!("{:?}", err); 104 | let outcome = RawAbiCallResult::AbiError(AbiErrorMsg { 105 | error_msg_utf8: err_str.as_ptr(), 106 | len: err_str.len(), 107 | }); 108 | unsafe { __savefile_internal_receiver(&outcome as *const _, abi_result) } 109 | } 110 | } 111 | } 112 | 1u16 => { 113 | let arg_cnt; 114 | arg_cnt = ::deserialize(&mut deserializer)?; 115 | unsafe { (&mut *trait_object.as_mut_ptr::()).advance_mut(arg_cnt) }; 116 | } 117 | 2u16 => { 118 | let ret = unsafe { &mut *trait_object.as_mut_ptr::() }.chunk_mut(); 119 | let mut __savefile_internal_data = FlexBuffer::new(); 120 | let mut serializer = Serializer { 121 | writer: &mut __savefile_internal_data, 122 | file_version: 0u32, 123 | }; 124 | serializer.write_u32(effective_version)?; 125 | 126 | match unsafe { serializer.write_raw_ptr_size(ret.as_mut_ptr(), ret.len()) } { 127 | Ok(()) => { 128 | let outcome = RawAbiCallResult::Success { 129 | data: __savefile_internal_data.as_ptr() as *const u8, 130 | len: __savefile_internal_data.len(), 131 | }; 132 | unsafe { __savefile_internal_receiver(&outcome as *const _, abi_result) } 133 | } 134 | Err(err) => { 135 | let err_str = format!("{:?}", err); 136 | let outcome = RawAbiCallResult::AbiError(AbiErrorMsg { 137 | error_msg_utf8: err_str.as_ptr(), 138 | len: err_str.len(), 139 | }); 140 | unsafe { __savefile_internal_receiver(&outcome as *const _, abi_result) } 141 | } 142 | } 143 | } 144 | _ => { 145 | return Err(SavefileError::general("Unknown method number")); 146 | } 147 | } 148 | Ok(()) 149 | } 150 | } 151 | unsafe impl BufMut for AbiConnection { 152 | #[inline] 153 | fn remaining_mut(&self) -> usize { 154 | let info: &AbiConnectionMethod = &self.template.methods[0u16 as usize]; 155 | let Some(callee_method_number) = info.callee_method_number else { 156 | panic!("Method \'{0}\' does not exist in implementation.", info.method_name,); 157 | }; 158 | let mut result_buffer = MaybeUninit::>::uninit(); 159 | let compatibility_mask = info.compatibility_mask; 160 | let mut __savefile_internal_datarawdata = [0u8; 4usize]; 161 | let mut __savefile_internal_data = Cursor::new(&mut __savefile_internal_datarawdata[..]); 162 | let mut serializer = Serializer { 163 | writer: &mut __savefile_internal_data, 164 | file_version: self.template.effective_version, 165 | }; 166 | serializer.write_u32(self.template.effective_version).unwrap(); 167 | unsafe { 168 | unsafe extern "C" fn abi_result_receiver(outcome: *const RawAbiCallResult, result_receiver: *mut ()) { 169 | let outcome = unsafe { &*outcome }; 170 | let result_receiver = 171 | unsafe { &mut *(result_receiver as *mut std::mem::MaybeUninit>) }; 172 | result_receiver.write(parse_return_value_impl( 173 | outcome, 174 | |deserializer| -> Result { 175 | ::deserialize(deserializer) 176 | }, 177 | )); 178 | } 179 | (self.template.entry)(AbiProtocol::RegularCall { 180 | trait_object: self.trait_object, 181 | compatibility_mask, 182 | method_number: callee_method_number, 183 | effective_version: self.template.effective_version, 184 | data: __savefile_internal_datarawdata[..].as_ptr(), 185 | data_length: 4usize, 186 | abi_result: &mut result_buffer as *mut _ as *mut (), 187 | receiver: abi_result_receiver, 188 | }); 189 | } 190 | let resval = unsafe { result_buffer.assume_init() }; 191 | resval.expect("Unexpected panic in invocation target") 192 | } 193 | #[inline] 194 | unsafe fn advance_mut(&mut self, arg_cnt: usize) { 195 | let info: &AbiConnectionMethod = &self.template.methods[1u16 as usize]; 196 | let Some(callee_method_number) = info.callee_method_number else { 197 | panic!("Method \'{0}\' does not exist in implementation.", info.method_name,); 198 | }; 199 | let mut result_buffer = MaybeUninit::>::new(Ok(())); 200 | let compatibility_mask = info.compatibility_mask; 201 | let mut __savefile_internal_datarawdata = [0u8; 12usize]; 202 | let mut __savefile_internal_data = Cursor::new(&mut __savefile_internal_datarawdata[..]); 203 | let mut serializer = Serializer { 204 | writer: &mut __savefile_internal_data, 205 | file_version: self.template.effective_version, 206 | }; 207 | serializer.write_u32(self.template.effective_version).unwrap(); 208 | arg_cnt.serialize(&mut serializer).expect("Failed while serializing"); 209 | debug_assert_eq!(std::mem::size_of_val(&arg_cnt), 8); 210 | unsafe { 211 | unsafe extern "C" fn abi_result_receiver(outcome: *const RawAbiCallResult, result_receiver: *mut ()) { 212 | let outcome = unsafe { &*outcome }; 213 | let result_receiver = 214 | unsafe { &mut *(result_receiver as *mut std::mem::MaybeUninit>) }; 215 | result_receiver.write(parse_return_value_impl(outcome, |_| -> Result<(), SavefileError> { 216 | Ok(()) 217 | })); 218 | } 219 | (self.template.entry)(AbiProtocol::RegularCall { 220 | trait_object: self.trait_object, 221 | compatibility_mask, 222 | method_number: callee_method_number, 223 | effective_version: self.template.effective_version, 224 | data: __savefile_internal_datarawdata.as_ptr() as *const u8, 225 | data_length: 12, 226 | abi_result: &mut result_buffer as *mut _ as *mut (), 227 | receiver: abi_result_receiver, 228 | }); 229 | } 230 | let resval = unsafe { result_buffer.assume_init() }; 231 | resval.expect("Unexpected panic in invocation target") 232 | } 233 | #[inline] 234 | fn chunk_mut(&mut self) -> &mut UninitSlice { 235 | let info: &AbiConnectionMethod = &self.template.methods[2u16 as usize]; 236 | let Some(callee_method_number) = info.callee_method_number else { 237 | panic!("Method \'{0}\' does not exist in implementation.", info.method_name,); 238 | }; 239 | let mut result_buffer = MaybeUninit::>::uninit(); 240 | let compatibility_mask = info.compatibility_mask; 241 | let mut __savefile_internal_datarawdata = [0u8; 4usize]; 242 | let mut __savefile_internal_data = Cursor::new(&mut __savefile_internal_datarawdata[..]); 243 | let mut serializer = Serializer { 244 | writer: &mut __savefile_internal_data, 245 | file_version: self.template.effective_version, 246 | }; 247 | serializer.write_u32(self.template.effective_version).unwrap(); 248 | unsafe { 249 | unsafe extern "C" fn abi_result_receiver(outcome: *const RawAbiCallResult, result_receiver: *mut ()) { 250 | let outcome = unsafe { &*outcome }; 251 | let result_receiver = unsafe { 252 | &mut *(result_receiver as *mut std::mem::MaybeUninit>) 253 | }; 254 | result_receiver.write(parse_return_value_impl( 255 | outcome, 256 | |deserializer| -> Result<&mut UninitSlice, SavefileError> { 257 | let iptr: *mut u8 = deserializer.read_raw_ptr_mut()?; 258 | let len = deserializer.read_usize()?; 259 | let bytes = slice::from_raw_parts_mut(iptr, len); 260 | Ok(UninitSlice::new(bytes)) 261 | }, 262 | )); 263 | } 264 | (self.template.entry)(AbiProtocol::RegularCall { 265 | trait_object: self.trait_object, 266 | compatibility_mask, 267 | method_number: callee_method_number, 268 | effective_version: self.template.effective_version, 269 | data: __savefile_internal_datarawdata[..].as_ptr(), 270 | data_length: 4usize, 271 | abi_result: &mut result_buffer as *mut _ as *mut (), 272 | receiver: abi_result_receiver, 273 | }); 274 | } 275 | let resval = unsafe { result_buffer.assume_init() }; 276 | resval.expect("Unexpected panic in invocation target") 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /savefile-test/src/test_introspect.rs: -------------------------------------------------------------------------------- 1 | use crate::roundtrip; 2 | use parking_lot::{Mutex, RwLock}; 3 | use savefile::prelude::*; 4 | use savefile::{IntrospectedElementKey, IntrospectionError, Introspector, IntrospectorNavCommand}; 5 | use std::cell::RefCell; 6 | use std::collections::BinaryHeap; 7 | use std::rc::Rc; 8 | use std::sync::Arc; 9 | 10 | #[derive(Savefile)] 11 | pub struct OtherStruct(u32, u16); 12 | 13 | #[derive(Savefile)] 14 | pub struct SillyStruct {} 15 | 16 | #[derive(Savefile)] 17 | pub enum SimpleEnum { 18 | VariantA(u32, u16, u8), 19 | VariantB { x: i32, y: i8 }, 20 | VariantC, 21 | } 22 | 23 | #[derive(Savefile)] 24 | pub struct SimpleStruct { 25 | item1: u32, 26 | } 27 | 28 | #[derive(Savefile)] 29 | pub struct ComplexStruct { 30 | simple1: SimpleStruct, 31 | simple2: SimpleStruct, 32 | an_int: u32, 33 | } 34 | #[derive(Savefile)] 35 | pub struct ComplexStructWithIgnoredField { 36 | simple1: SimpleStruct, 37 | #[savefile_introspect_ignore] 38 | simple2: SimpleStruct, 39 | an_int: u32, 40 | } 41 | 42 | #[derive(Savefile)] 43 | pub struct StructWithName { 44 | #[savefile_introspect_key] 45 | name: String, 46 | value: String, 47 | } 48 | 49 | #[derive(Savefile)] 50 | pub enum EnumWithName { 51 | Variant1(#[savefile_introspect_key] String), 52 | Variant2 { 53 | #[savefile_introspect_key] 54 | name: String, 55 | value: String, 56 | }, 57 | Variant3, 58 | } 59 | 60 | #[test] 61 | pub fn test_simple_enum_with_key() { 62 | let var1 = EnumWithName::Variant1("Hejsan".into()); 63 | let var2 = EnumWithName::Variant2 { 64 | name: "James".into(), 65 | value: "IV".into(), 66 | }; 67 | let var3 = EnumWithName::Variant3; 68 | assert_eq!(var1.introspect_len(), 1); 69 | assert_eq!(var2.introspect_len(), 2); 70 | assert_eq!(var3.introspect_len(), 0); 71 | assert_eq!(var1.introspect_value(), "Hejsan"); 72 | assert_eq!(var2.introspect_value(), "James"); 73 | assert_eq!(var3.introspect_value(), "EnumWithName::Variant3"); 74 | } 75 | 76 | #[test] 77 | pub fn test_simple_with_key() { 78 | let val1 = StructWithName { 79 | name: "Apple".into(), 80 | value: "Orange".into(), 81 | }; 82 | assert_eq!(val1.introspect_len(), 2); 83 | assert_eq!(val1.introspect_value(), "Apple"); 84 | } 85 | 86 | #[test] 87 | pub fn test_simple_binheap() { 88 | let mut heap = BinaryHeap::new(); 89 | heap.push(43); 90 | assert_eq!(heap.introspect_len(), 1); 91 | assert_eq!(heap.introspect_value(), "BinaryHeap"); 92 | assert_eq!(heap.introspect_child(0).unwrap().val().introspect_value(), "43"); 93 | } 94 | 95 | #[derive(Savefile)] 96 | pub struct StructWithNameAndIgnoredValue { 97 | name: String, 98 | #[savefile_introspect_ignore] 99 | value: String, 100 | } 101 | #[test] 102 | pub fn test_simple_with_ignored_value() { 103 | let val1 = StructWithNameAndIgnoredValue { 104 | name: "Apple".into(), 105 | value: "Orange".into(), 106 | }; 107 | assert_eq!(val1.introspect_len(), 1); 108 | assert_eq!(val1.introspect_child(0).unwrap().key(), "name"); 109 | assert_eq!(val1.introspect_child(0).unwrap().val().introspect_value(), "Apple"); 110 | } 111 | 112 | #[test] 113 | pub fn test_simple_enum() { 114 | let val1 = SimpleEnum::VariantA(11, 12, 13); 115 | assert_eq!(val1.introspect_len(), 3); 116 | assert_eq!(val1.introspect_child(0).unwrap().key(), "0"); 117 | assert_eq!(val1.introspect_child(0).unwrap().val().introspect_value(), "11"); 118 | assert_eq!(val1.introspect_child(1).unwrap().key(), "1"); 119 | assert_eq!(val1.introspect_child(1).unwrap().val().introspect_value(), "12"); 120 | assert_eq!(val1.introspect_child(2).unwrap().key(), "2"); 121 | assert_eq!(val1.introspect_child(2).unwrap().val().introspect_value(), "13"); 122 | 123 | let val2 = SimpleEnum::VariantB { x: 74, y: 32 }; 124 | assert_eq!(val2.introspect_len(), 2); 125 | assert_eq!(val2.introspect_child(0).unwrap().key(), "x"); 126 | assert_eq!(val2.introspect_child(0).unwrap().val().introspect_value(), "74"); 127 | assert_eq!(val2.introspect_child(1).unwrap().key(), "y"); 128 | assert_eq!(val2.introspect_child(1).unwrap().val().introspect_value(), "32"); 129 | 130 | let val3 = SimpleEnum::VariantC; 131 | assert_eq!(val3.introspect_len(), 0); 132 | assert_eq!(val3.introspect_value(), "SimpleEnum::VariantC"); 133 | } 134 | 135 | #[test] 136 | pub fn do_test_silly_struct() { 137 | let test = SillyStruct {}; 138 | assert_eq!(test.introspect_value(), "SillyStruct"); 139 | assert_eq!(test.introspect_len(), 0); 140 | } 141 | 142 | #[test] 143 | pub fn do_test1() { 144 | let test = SimpleStruct { item1: 342 }; 145 | 146 | let x = (&test).introspect_value(); 147 | assert_eq!(x, "SimpleStruct"); 148 | 149 | assert_eq!(test.introspect_len(), 1); 150 | assert_eq!(test.introspect_child(0).unwrap().key(), "item1"); 151 | assert_eq!(test.introspect_child(0).unwrap().val().introspect_value(), "342"); 152 | } 153 | #[test] 154 | pub fn do_test_refcell() { 155 | let test = RefCell::new(32); 156 | 157 | let x = (&test).introspect_value(); 158 | assert_eq!(x, "RefCell(Ref(32))"); 159 | 160 | assert_eq!(test.introspect_len(), 1); 161 | 162 | assert_eq!(test.introspect_child(0).unwrap().val().introspect_value(), "Ref(32)"); 163 | } 164 | #[test] 165 | pub fn do_test_rc() { 166 | let test = Rc::new(32); 167 | 168 | let x = (&test).introspect_value(); 169 | assert_eq!(x, "Rc(32)"); 170 | 171 | assert_eq!(test.introspect_len(), 0); 172 | } 173 | #[test] 174 | pub fn do_test_arc() { 175 | let test = Arc::new(32); 176 | 177 | let x = (&test).introspect_value(); 178 | assert_eq!(x, "Arc(32)"); 179 | 180 | assert_eq!(test.introspect_len(), 0); 181 | } 182 | 183 | #[test] 184 | pub fn do_test_rwlock() { 185 | let test = RwLock::new(SimpleStruct { item1: 342 }); 186 | 187 | let _x = (&test).introspect_value(); 188 | 189 | assert_eq!(test.introspect_len(), 1); 190 | assert_eq!(test.introspect_child(0).unwrap().key(), "0"); 191 | assert_eq!( 192 | test.introspect_child(0).unwrap().val().introspect_value(), 193 | "SimpleStruct" 194 | ); 195 | 196 | let temp = test.introspect_child(0).unwrap(); 197 | let temp2 = temp.val().introspect_child(0).unwrap(); 198 | assert_eq!(temp2.key(), "item1"); 199 | let subchild = temp2.val(); 200 | assert_eq!(subchild.introspect_len(), 0); 201 | assert_eq!(subchild.introspect_value(), "342"); 202 | } 203 | 204 | #[test] 205 | pub fn do_test_mutex() { 206 | let test = Mutex::new(SimpleStruct { item1: 343 }); 207 | 208 | let _x = (&test).introspect_value(); 209 | 210 | assert_eq!(test.introspect_len(), 1); 211 | assert_eq!(test.introspect_child(0).unwrap().key(), "0"); 212 | assert_eq!( 213 | test.introspect_child(0).unwrap().val().introspect_value(), 214 | "SimpleStruct" 215 | ); 216 | 217 | let temp = test.introspect_child(0).unwrap(); 218 | let temp2 = temp.val().introspect_child(0).unwrap(); 219 | assert_eq!(temp2.key(), "item1"); 220 | let subchild = temp2.val(); 221 | assert_eq!(subchild.introspect_len(), 0); 222 | assert_eq!(subchild.introspect_value(), "343"); 223 | } 224 | 225 | #[test] 226 | pub fn func_to_do_stuff() { 227 | let os = OtherStruct(43, 32); 228 | 229 | assert_eq!(os.introspect_len(), 2); 230 | assert_eq!(os.introspect_child(0).unwrap().key(), "0"); 231 | assert_eq!(os.introspect_child(0).unwrap().val().introspect_value(), "43"); 232 | assert_eq!(os.introspect_child(1).unwrap().key(), "1"); 233 | assert_eq!(os.introspect_child(1).unwrap().val().introspect_value(), "32"); 234 | } 235 | 236 | #[test] 237 | pub fn test_introspect_no_children() { 238 | let mut base_introspector = Introspector::new(); 239 | assert_eq!( 240 | base_introspector 241 | .do_introspect( 242 | &0u32, 243 | IntrospectorNavCommand::SelectNth { 244 | select_depth: 0, 245 | select_index: 0 246 | } 247 | ) 248 | .unwrap_err(), 249 | IntrospectionError::NoChildren 250 | ); 251 | } 252 | #[test] 253 | pub fn test_introspector_simpler_case0() { 254 | let comp = ComplexStruct { 255 | simple1: SimpleStruct { item1: 37 }, 256 | simple2: SimpleStruct { item1: 38 }, 257 | an_int: 4, 258 | }; 259 | 260 | let mut base_introspector = Introspector::new(); 261 | 262 | let result = base_introspector 263 | .do_introspect( 264 | &comp, 265 | IntrospectorNavCommand::SelectNth { 266 | select_depth: 0, 267 | select_index: 0, 268 | }, 269 | ) 270 | .unwrap(); 271 | assert_eq!(result.frames.len(), 2); 272 | let result = base_introspector 273 | .do_introspect( 274 | &comp, 275 | IntrospectorNavCommand::SelectNth { 276 | select_depth: 1, 277 | select_index: 0, 278 | }, 279 | ) 280 | .expect("Leafs should also be selectable"); 281 | 282 | assert_eq!(result.frames.len(), 2); 283 | assert_eq!(result.frames[1].keyvals[0].selected, true); 284 | } 285 | #[test] 286 | pub fn test_introspector_simpler_case1() { 287 | let comp = ComplexStruct { 288 | simple1: SimpleStruct { item1: 37 }, 289 | simple2: SimpleStruct { item1: 38 }, 290 | an_int: 4, 291 | }; 292 | 293 | let mut base_introspector = Introspector::new(); 294 | 295 | let result = base_introspector 296 | .do_introspect( 297 | &comp, 298 | IntrospectorNavCommand::ExpandElement(IntrospectedElementKey { 299 | key_disambiguator: 0, 300 | key: "simple2".to_string(), 301 | depth: 0, 302 | }), 303 | ) 304 | .unwrap(); 305 | 306 | assert_eq!(result.frames.len(), 2); 307 | } 308 | 309 | #[test] 310 | pub fn test_introspector_simple_case_ignored_field() { 311 | let comp = ComplexStructWithIgnoredField { 312 | simple1: SimpleStruct { item1: 37 }, 313 | simple2: SimpleStruct { item1: 38 }, 314 | an_int: 4, 315 | }; 316 | 317 | let mut base_introspector = Introspector::new(); 318 | 319 | { 320 | let result = base_introspector 321 | .do_introspect(&comp, IntrospectorNavCommand::Nothing) 322 | .unwrap(); 323 | 324 | assert_eq!(result.frames[0].keyvals.len(), 2); 325 | assert_eq!(result.frames[0].keyvals[0].key.key, "simple1"); 326 | assert_eq!(result.frames[0].keyvals[1].key.key, "an_int"); 327 | } 328 | } 329 | #[test] 330 | pub fn test_introspector_simple_case() { 331 | let comp = ComplexStruct { 332 | simple1: SimpleStruct { item1: 37 }, 333 | simple2: SimpleStruct { item1: 38 }, 334 | an_int: 4, 335 | }; 336 | 337 | let mut base_introspector = Introspector::new(); 338 | 339 | { 340 | base_introspector 341 | .do_introspect(&comp, IntrospectorNavCommand::Nothing) 342 | .unwrap(); 343 | 344 | assert_eq!( 345 | base_introspector 346 | .do_introspect(&comp, IntrospectorNavCommand::Up) 347 | .unwrap_err(), 348 | IntrospectionError::AlreadyAtTop 349 | ); 350 | assert_eq!( 351 | base_introspector 352 | .do_introspect( 353 | &comp, 354 | IntrospectorNavCommand::SelectNth { 355 | select_depth: 0, 356 | select_index: 3 357 | } 358 | ) 359 | .unwrap_err(), 360 | IntrospectionError::IndexOutOfRange 361 | ); 362 | assert_eq!( 363 | base_introspector 364 | .do_introspect( 365 | &comp, 366 | IntrospectorNavCommand::ExpandElement(IntrospectedElementKey { 367 | key: "simple1".into(), 368 | key_disambiguator: 0, 369 | depth: 1 370 | }) 371 | ) 372 | .unwrap_err(), 373 | IntrospectionError::BadDepth 374 | ); 375 | assert_eq!( 376 | base_introspector 377 | .do_introspect( 378 | &comp, 379 | IntrospectorNavCommand::ExpandElement(IntrospectedElementKey { 380 | key: "simple3".into(), 381 | key_disambiguator: 0, 382 | depth: 0 383 | }) 384 | ) 385 | .unwrap_err(), 386 | IntrospectionError::UnknownKey 387 | ); 388 | } 389 | println!("Base introspector : {:?}", base_introspector); 390 | 391 | let result = base_introspector 392 | .do_introspect( 393 | &comp, 394 | IntrospectorNavCommand::SelectNth { 395 | select_depth: 0, 396 | select_index: 0, 397 | }, 398 | ) 399 | .unwrap(); 400 | println!("Result: {:?}", result); 401 | assert_eq!(result.frames.len(), 2); 402 | assert_eq!(result.total_len(), 4); 403 | assert_eq!(result.total_index(0).unwrap().key.key, "simple1"); 404 | assert_eq!(result.total_index(0).unwrap().value, "SimpleStruct"); 405 | assert_eq!(result.total_index(1).unwrap().key.key, "item1"); 406 | assert_eq!(result.total_index(1).unwrap().value, "37"); 407 | assert_eq!(result.total_index(2).unwrap().key.key, "simple2"); 408 | assert_eq!(result.total_index(2).unwrap().value, "SimpleStruct"); 409 | assert_eq!(result.total_index(3).unwrap().key.key, "an_int"); 410 | assert_eq!(result.total_index(3).unwrap().value, "4"); 411 | assert_eq!(result.total_index(4), None); 412 | 413 | { 414 | let mut introspector = base_introspector.clone(); 415 | let result = introspector 416 | .do_introspect(&comp, IntrospectorNavCommand::Nothing) 417 | .unwrap(); 418 | assert_eq!(result.frames.len(), 2); 419 | 420 | println!("Result debug: {:?}", result); 421 | let disp = format!("{}", result); 422 | 423 | println!("Distp: {:?}", disp); 424 | assert!(disp.contains("*simple1 = SimpleStruct")); 425 | assert!(disp.contains(">simple2 = SimpleStruct")); 426 | assert!(disp.contains(" item1 = 37")); 427 | assert!(!disp.contains(" item1 = 38")); //Not expanded 428 | assert!(disp.contains(" an_int = 4")); 429 | 430 | let result3 = introspector.do_introspect(&comp, IntrospectorNavCommand::Up).unwrap(); 431 | assert_eq!(result3.frames.len(), 1); 432 | } 433 | 434 | { 435 | let mut introspector = base_introspector.clone(); 436 | let result = introspector 437 | .do_introspect( 438 | &comp, 439 | IntrospectorNavCommand::ExpandElement(IntrospectedElementKey { 440 | depth: 0, 441 | key: "simple2".to_string(), 442 | ..Default::default() 443 | }), 444 | ) 445 | .unwrap(); 446 | let disp = format!("{}", result); 447 | assert_eq!(result.frames.len(), 2); 448 | assert!(disp.contains(">simple1 = SimpleStruct")); 449 | assert!(disp.contains("*simple2 = SimpleStruct")); 450 | assert!(!disp.contains(" item1 = 37")); //Now expanded 451 | assert!(disp.contains(" item1 = 38")); //Now expanded 452 | } 453 | } 454 | 455 | #[derive(Savefile)] 456 | struct NestableStruct { 457 | a: Option>, 458 | b: Option>, 459 | c: u32, 460 | } 461 | 462 | #[test] 463 | pub fn test_introspector_deeply_nested_case() { 464 | let sample = NestableStruct { 465 | a: Some(Box::new(NestableStruct { 466 | a: Some(Box::new(NestableStruct { a: None, b: None, c: 1 })), 467 | b: Some(Box::new(NestableStruct { a: None, b: None, c: 2 })), 468 | c: 3, 469 | })), 470 | b: Some(Box::new(NestableStruct { 471 | a: Some(Box::new(NestableStruct { a: None, b: None, c: 4 })), 472 | b: Some(Box::new(NestableStruct { a: None, b: None, c: 5 })), 473 | c: 6, 474 | })), 475 | c: 7, 476 | }; 477 | 478 | let mut introspector = Introspector::new(); 479 | 480 | let _ = introspector 481 | .do_introspect( 482 | &sample, 483 | IntrospectorNavCommand::SelectNth { 484 | select_depth: 0, 485 | select_index: 0, 486 | }, 487 | ) 488 | .unwrap(); 489 | let result = introspector 490 | .do_introspect( 491 | &sample, 492 | IntrospectorNavCommand::SelectNth { 493 | select_depth: 0, 494 | select_index: 0, 495 | }, 496 | ) 497 | .unwrap(); 498 | assert_eq!(result.frames[0].keyvals[0].key.key, "a"); 499 | assert_eq!(result.frames[0].keyvals[1].key.key, "b"); 500 | assert_eq!(result.frames[0].keyvals[2].key.key, "c"); 501 | assert_eq!(result.frames[0].keyvals[2].value, "7"); 502 | 503 | let disp = format!("{}", result); 504 | assert_eq!( 505 | disp, 506 | "Introspectionresult: 507 | *a = Some(NestableStruct) 508 | *a = Some(NestableStruct) 509 | a = None 510 | b = None 511 | c = 1 512 | >b = Some(NestableStruct) 513 | c = 3 514 | >b = Some(NestableStruct) 515 | c = 7 516 | " 517 | ); 518 | } 519 | --------------------------------------------------------------------------------