diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 171543ec..fcd384e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -43,6 +45,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -73,6 +77,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -95,6 +101,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -110,8 +118,11 @@ jobs: target key: ${{ runner.os }}-cargo-embedded-${{ hashFiles('**/Cargo.lock') }} - - name: Check embedded core - run: cargo check --package aimdb-core --target thumbv7em-none-eabihf --features embedded + - name: Check embedded core (no_std minimal) + run: cargo check --package aimdb-core --target thumbv7em-none-eabihf --no-default-features + + - name: Check embedded core (with embassy) + run: cargo check --package aimdb-core --target thumbv7em-none-eabihf --no-default-features --features embassy-runtime - name: Check embassy adapter run: cargo check --package aimdb-embassy-adapter --target thumbv7em-none-eabihf --features embassy-runtime @@ -130,6 +141,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -173,6 +186,8 @@ jobs: needs: [format-and-lint, makefile-build, feature-validation, embedded-targets, adapters] steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -192,8 +207,5 @@ jobs: - name: Run comprehensive development check run: make check - - name: Build examples - run: cargo build --package aimdb-examples - - name: Generate documentation run: make doc diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 208747a0..f4480342 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -79,6 +79,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -163,6 +165,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ec3e0776..539b0c06 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,6 +18,8 @@ jobs: environment: ${{ github.ref == 'refs/heads/main' && 'github-pages' || null }} steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -41,7 +43,7 @@ jobs: cp -r target/doc/* target/doc-final/cloud/ # Build embedded documentation - cargo doc --features "embedded,embassy-runtime" --no-deps + cargo doc --no-default-features --features "embassy-runtime" --no-deps cp -r target/doc/* target/doc-final/embedded/ # Copy main index page diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 41e11a00..35f48c62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index adc4099a..c231ab99 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -23,6 +23,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable @@ -38,6 +40,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Install Rust uses: dtolnay/rust-toolchain@stable diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..00967e60 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "_external/embassy"] + path = _external/embassy + url = https://github.com/embassy-rs/embassy.git diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee8fa329..163c0cb0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -144,11 +144,11 @@ embedded = ["no-std-compat"] # All tests with all features make test -# Embedded target tests -cargo test --features embedded --target thumbv7em-none-eabihf +# Embedded target cross-compilation check +make test-embedded # Specific test -cargo test test_name --all-features +cargo test test_name --features tokio-runtime ``` ### Test Requirements diff --git a/Cargo.lock b/Cargo.lock index a16ac585..08aeff32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aimdb-cli" version = "0.1.0" @@ -37,11 +46,15 @@ version = "0.1.0" name = "aimdb-core" version = "0.1.0" dependencies = [ + "aimdb-executor", + "aimdb-macros", "anyhow", "embassy-executor", - "heapless", + "heapless 0.9.1", "metrics", + "serde", "serde_json", + "spin", "thiserror", "tokio", "tracing", @@ -52,25 +65,80 @@ name = "aimdb-embassy-adapter" version = "0.1.0" dependencies = [ "aimdb-core", + "aimdb-executor", + "defmt 1.0.1", "embassy-executor", + "embassy-sync", "embassy-time", - "embedded-hal 1.0.0", + "embedded-hal 0.2.7", "embedded-hal-async", "embedded-hal-nb", - "heapless", + "futures", + "heapless 0.9.1", + "rand", + "tracing", + "tracing-test", +] + +[[package]] +name = "aimdb-examples-shared" +version = "0.1.0" +dependencies = [ + "aimdb-core", + "aimdb-executor", + "defmt 0.3.100", ] [[package]] -name = "aimdb-examples" +name = "aimdb-executor" version = "0.1.0" +dependencies = [ + "embassy-executor", + "thiserror", + "tokio", +] + +[[package]] +name = "aimdb-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "trybuild", +] [[package]] name = "aimdb-tokio-adapter" version = "0.1.0" dependencies = [ "aimdb-core", + "aimdb-executor", + "futures", "tokio", "tokio-test", + "tracing", +] + +[[package]] +name = "aimdb-tokio-demo" +version = "0.1.0" +dependencies = [ + "aimdb-core", + "aimdb-examples-shared", + "aimdb-executor", + "aimdb-tokio-adapter", + "tokio", + "tracing", +] + +[[package]] +name = "aligned" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" +dependencies = [ + "as-slice", ] [[package]] @@ -79,6 +147,15 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -98,9 +175,15 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "backtrace" version = "0.3.76" @@ -113,15 +196,63 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "block-device-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c051592f59fe68053524b4c4935249b806f72c1f544cfb7abe4f57c3be258e" +dependencies = [ + "aligned", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -134,12 +265,71 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "critical-section", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -167,7 +357,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.106", ] [[package]] @@ -178,7 +368,58 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.106", +] + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cac3b8a5644a9e02b75085ebad3b6deafdbdbdec04bb25086523828aa4dfd1" +dependencies = [ + "critical-section", + "defmt 1.0.1", ] [[package]] @@ -190,13 +431,31 @@ dependencies = [ "litrs", ] +[[package]] +name = "embassy-embedded-hal" +version = "0.5.0" +dependencies = [ + "defmt 1.0.1", + "embassy-futures", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + [[package]] name = "embassy-executor" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" dependencies = [ + "cordyceps", + "cortex-m", "critical-section", + "defmt 1.0.1", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -205,29 +464,166 @@ dependencies = [ [[package]] name = "embassy-executor-macros" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] name = "embassy-executor-timer-queue" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" + +[[package]] +name = "embassy-hal-internal" +version = "0.3.0" +dependencies = [ + "cortex-m", + "critical-section", + "defmt 1.0.1", + "num-traits", +] + +[[package]] +name = "embassy-net" +version = "0.7.1" +dependencies = [ + "defmt 1.0.1", + "document-features", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "embedded-io-async", + "embedded-nal-async", + "heapless 0.8.0", + "managed", + "smoltcp", +] + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.2" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-runtime-demo" +version = "0.1.0" +dependencies = [ + "aimdb-core", + "aimdb-embassy-adapter", + "aimdb-examples-shared", + "aimdb-executor", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 1.0.1", + "defmt-rtt", + "embassy-executor", + "embassy-futures", + "embassy-net", + "embassy-stm32", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embedded-alloc", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-io-async", + "embedded-nal-async", + "embedded-storage", + "heapless 0.8.0", + "micromath", + "panic-probe", + "static_cell", + "stm32-fmc", +] + +[[package]] +name = "embassy-stm32" +version = "0.4.0" +dependencies = [ + "aligned", + "bit_field", + "bitflags 2.9.4", + "block-device-driver", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 1.0.1", + "document-features", + "embassy-embedded-hal", + "embassy-executor", + "embassy-futures", + "embassy-hal-internal", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embassy-time-queue-utils", + "embassy-usb-driver", + "embassy-usb-synopsys-otg", + "embedded-can", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "embedded-storage", + "embedded-storage-async", + "futures-util", + "heapless 0.9.1", + "nb 1.1.0", + "proc-macro2", + "quote", + "rand_core 0.6.4", + "rand_core 0.9.3", + "sdio-host", + "static_assertions", + "stm32-fmc", + "stm32-metapac", + "vcell", + "volatile-register", +] + +[[package]] +name = "embassy-sync" +version = "0.7.2" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 1.0.1", + "embedded-io-async", + "futures-core", + "futures-sink", + "heapless 0.8.0", +] [[package]] name = "embassy-time" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" dependencies = [ "cfg-if", "critical-section", + "defmt 1.0.1", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -239,12 +635,72 @@ dependencies = [ [[package]] name = "embassy-time-driver" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" dependencies = [ "document-features", ] +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.0" +dependencies = [ + "embassy-executor-timer-queue", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-usb" +version = "0.5.1" +dependencies = [ + "defmt 1.0.1", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-usb-driver", + "embedded-io-async", + "heapless 0.8.0", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.2.0" +dependencies = [ + "defmt 1.0.1", + "embedded-io-async", +] + +[[package]] +name = "embassy-usb-synopsys-otg" +version = "0.3.1" +dependencies = [ + "critical-section", + "defmt 1.0.1", + "embassy-sync", + "embassy-usb-driver", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-can" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" +dependencies = [ + "nb 1.1.0", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -281,10 +737,106 @@ dependencies = [ ] [[package]] -name = "fnv" -version = "1.0.7" +name = "embedded-io" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +dependencies = [ + "defmt 0.3.100", +] + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "defmt 0.3.100", + "embedded-io", +] + +[[package]] +name = "embedded-nal" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56a28be191a992f28f178ec338a0bf02f63d7803244add736d026a471e6ed77" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "embedded-nal-async" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76959917cd2b86f40a98c28dd5624eddd1fa69d746241c8257eac428d83cb211" +dependencies = [ + "embedded-io-async", + "embedded-nal", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] [[package]] name = "futures-core" @@ -292,12 +844,101 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hash32" version = "0.3.1" @@ -307,6 +948,32 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "defmt 0.3.100", + "hash32", + "stable_deref_trait", +] + [[package]] name = "heapless" version = "0.9.1" @@ -323,13 +990,23 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags", + "bitflags 2.9.4", "cfg-if", "libc", ] @@ -340,11 +1017,23 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" [[package]] name = "litrs" @@ -352,6 +1041,49 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.7.6" @@ -368,6 +1100,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "micromath" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -385,7 +1123,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -403,6 +1141,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.37.3" @@ -418,18 +1174,88 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt 1.0.1", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -439,15 +1265,98 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "producer-consumer-demo" +version = "0.1.0" +dependencies = [ + "aimdb-core", + "aimdb-tokio-adapter", + "tokio", +] + [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "rlsf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "svgbobdoc", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -455,70 +1364,256 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] -name = "ryu" -version = "1.0.20" +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdio-host" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b328e2cb950eeccd55b7f55c3a963691455dcd044cfb5354f0c5e68d2c2d6ee2" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smoltcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt 0.3.100", + "heapless 0.8.0", + "managed", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "serde" -version = "1.0.226" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" -dependencies = [ - "serde_core", -] +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "serde_core" -version = "1.0.226" +name = "static_cell" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" dependencies = [ - "serde_derive", + "portable-atomic", ] [[package]] -name = "serde_derive" -version = "1.0.226" +name = "stm32-fmc" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "c7f0639399e2307c2446c54d91d4f1596343a1e1d5cab605b9cce11d0ab3858c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "embedded-hal 0.2.7", ] [[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +name = "stm32-metapac" +version = "18.0.0" +source = "git+https://github.com/embassy-rs/stm32-data-generated?tag=stm32-data-b9f6b0c542d85ee695d71c35ced195e0cef51ac0#9b8fb67703361e2237b6c1ec4f1ee5949223d412" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", + "cortex-m", + "cortex-m-rt", + "defmt 0.3.100", ] [[package]] -name = "slab" -version = "0.4.11" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "svgbobdoc" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] [[package]] -name = "strsim" -version = "0.11.1" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" @@ -531,24 +1626,48 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", ] [[package]] @@ -558,12 +1677,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", + "bytes", "io-uring", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "slab", + "socket2", "tokio-macros", + "windows-sys 0.59.0", ] [[package]] @@ -574,7 +1698,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -601,6 +1725,45 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + [[package]] name = "tracing" version = "0.1.41" @@ -608,14 +1771,95 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "trybuild" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] [[package]] name = "unicode-ident" @@ -623,6 +1867,71 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + [[package]] name = "version_check" version = "0.9.5" @@ -635,17 +1944,146 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] [[package]] name = "windows-sys" @@ -656,6 +2094,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -672,6 +2119,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -720,6 +2176,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + [[package]] name = "zerocopy" version = "0.8.27" @@ -737,5 +2199,5 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] diff --git a/Cargo.toml b/Cargo.toml index 4ec8b162..54df11ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,17 @@ [workspace] members = [ + "aimdb-executor", "aimdb-core", "aimdb-embassy-adapter", "aimdb-tokio-adapter", + "aimdb-macros", "tools/aimdb-cli", - "examples", + "examples/shared", + "examples/tokio-runtime-demo", + "examples/embassy-runtime-demo", + "examples/producer-consumer-demo", ] +exclude = ["_external"] resolver = "2" # Note: build aimdb-core only by default. Use make all to build the full workspace! default-members = ["aimdb-core"] @@ -23,7 +29,10 @@ categories = ["database-implementations", "embedded", "asynchronous"] [workspace.dependencies] # Core async runtime -tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.47.1", default-features = false, features = [ + "macros", + "rt-multi-thread", +] } # Serialization serde = { version = "1.0", features = ["derive"] } @@ -49,15 +58,72 @@ clap = { version = "4.0", features = ["derive"] } # Development dependencies tokio-test = "0.4" +aimdb-tokio-adapter = { path = "./aimdb-tokio-adapter" } # Embassy ecosystem for embedded async -embassy-executor = { version = "0.9.1" } -embassy-time = "0.5" +embassy-stm32 = { version = "0.4.0", path = "./_external/embassy/embassy-stm32", features = [ + "defmt", + "stm32h563zi", + "memory-x", + "time-driver-any", + "exti", + "unstable-pac", + "low-power", +] } +embassy-sync = { version = "0.7.2", path = "./_external/embassy/embassy-sync", features = [ + "defmt", +] } +embassy-executor = { version = "0.9.0", path = "./_external/embassy/embassy-executor", features = [ + "arch-cortex-m", + "executor-thread", + "defmt", +] } +embassy-time = { version = "0.5.0", path = "./_external/embassy/embassy-time", features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-net = { version = "0.7.1", path = "./_external/embassy/embassy-net", features = [ + "defmt", + "tcp", + "dhcpv4", + "medium-ethernet", + "proto-ipv6", +] } +embassy-usb = { version = "0.5.1", path = "./_external/embassy/embassy-usb", features = [ + "defmt", +] } +embassy-futures = { version = "0.1.2", path = "./_external/embassy/embassy-futures" } # Embedded HAL for peripheral abstractions -embedded-hal = "1.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = "1.0" embedded-hal-nb = "1.0" +embedded-hal-bus = { version = "0.2", features = ["async"] } +embedded-io = { version = "0.6.0" } +embedded-io-async = { version = "0.6.1" } +embedded-nal-async = "0.8.0" +embedded-storage = "0.3.1" + +# Embedded runtime and utilities +cortex-m = { version = "0.7.6", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +static_cell = "2" + +# Embedded debugging and logging +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } + +# Embedded utilities +heapless = { version = "0.8", default-features = false } +micromath = "2.0.0" +stm32-fmc = "0.3.0" [workspace.metadata.docs.rs] all-features = true diff --git a/Makefile b/Makefile index c63709fe..462fca7d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # AimDB Makefile # Simple automation for common development tasks -.PHONY: help build test clean fmt clippy doc all check test-embedded test-feature-validation +.PHONY: help build test clean fmt clippy doc all check test-embedded test-feature-validation examples .DEFAULT_GOAL := help # Colors for output @@ -18,6 +18,7 @@ help: @printf " $(YELLOW)Core Commands:$(NC)\n" @printf " build Build all components (std + embedded)\n" @printf " test Run all tests (std + embedded)\n" + @printf " examples Build all example projects\n" @printf " fmt Format code\n" @printf " clippy Run linter\n" @printf " doc Generate docs\n" @@ -34,21 +35,19 @@ help: ## Core commands build: @printf "$(GREEN)Building AimDB (all valid combinations)...$(NC)\n" - @printf "$(YELLOW) → Building aimdb-core (minimal embedded)$(NC)\n" - cargo build --package aimdb-core --features "embedded" + @printf "$(YELLOW) → Building aimdb-core (no_std minimal)$(NC)\n" + cargo build --package aimdb-core --no-default-features @printf "$(YELLOW) → Building aimdb-core (std platform)$(NC)\n" cargo build --package aimdb-core --features "std,tokio-runtime,tracing,metrics" @printf "$(YELLOW) → Building tokio adapter$(NC)\n" cargo build --package aimdb-tokio-adapter --features "tokio-runtime,tracing,metrics" - @printf "$(YELLOW) → Building embassy adapter$(NC)\n" - cargo build --package aimdb-embassy-adapter --features "embassy-runtime" @printf "$(YELLOW) → Building CLI tools$(NC)\n" cargo build --package aimdb-cli test: @printf "$(GREEN)Running all tests (valid combinations)...$(NC)\n" - @printf "$(YELLOW) → Testing aimdb-core (embedded platform)$(NC)\n" - cargo test --package aimdb-core --features "embedded" + @printf "$(YELLOW) → Testing aimdb-core (no_std minimal)$(NC)\n" + cargo test --package aimdb-core --no-default-features @printf "$(YELLOW) → Testing aimdb-core (std platform)$(NC)\n" cargo test --package aimdb-core --features "std,tokio-runtime,tracing" @printf "$(YELLOW) → Testing tokio adapter$(NC)\n" @@ -57,13 +56,17 @@ test: cargo test --package aimdb-cli fmt: - @printf "$(GREEN)Formatting code...$(NC)\n" - cargo fmt --all + @printf "$(GREEN)Formatting code (workspace members only)...$(NC)\n" + @for pkg in aimdb-executor aimdb-core aimdb-embassy-adapter aimdb-tokio-adapter aimdb-macros aimdb-cli aimdb-examples-shared aimdb-tokio-demo embassy-runtime-demo producer-consumer-demo; do \ + printf "$(YELLOW) → Formatting $$pkg$(NC)\n"; \ + cargo fmt -p $$pkg 2>/dev/null || true; \ + done + @printf "$(GREEN)✓ Formatting complete!$(NC)\n" clippy: @printf "$(GREEN)Running clippy (all valid combinations)...$(NC)\n" - @printf "$(YELLOW) → Clippy on aimdb-core (embedded)$(NC)\n" - cargo clippy --package aimdb-core --features "embedded" --all-targets -- -D warnings + @printf "$(YELLOW) → Clippy on aimdb-core (no_std)$(NC)\n" + cargo clippy --package aimdb-core --no-default-features --all-targets -- -D warnings @printf "$(YELLOW) → Clippy on aimdb-core (std)$(NC)\n" cargo clippy --package aimdb-core --features "std,tokio-runtime,tracing,metrics" --all-targets -- -D warnings @printf "$(YELLOW) → Clippy on tokio adapter$(NC)\n" @@ -82,7 +85,7 @@ doc: cargo doc --features "std,tokio-runtime,tracing,metrics" --no-deps @cp -r target/doc/* target/doc-final/cloud/ @printf "$(YELLOW) → Building embedded documentation$(NC)\n" - cargo doc --features "embedded,embassy-runtime" --no-deps + cargo doc --no-default-features --features "embassy-runtime" --no-deps @cp -r target/doc/* target/doc-final/embedded/ @printf "$(YELLOW) → Creating main index page$(NC)\n" @cp docs/index.html target/doc-final/index.html @@ -95,26 +98,37 @@ clean: ## Testing commands test-embedded: @printf "$(BLUE)Testing embedded/MCU cross-compilation compatibility...$(NC)\n" - @printf "$(YELLOW) → Checking aimdb-core (embedded) on thumbv7em-none-eabihf target$(NC)\n" - cargo check --package aimdb-core --target thumbv7em-none-eabihf --features "embedded" - @printf "$(YELLOW) → Checking aimdb-core (embassy-runtime) on thumbv7em-none-eabihf target$(NC)\n" - cargo check --package aimdb-core --target thumbv7em-none-eabihf --features "embedded,embassy-runtime" + @printf "$(YELLOW) → Checking aimdb-core (no_std minimal) on thumbv7em-none-eabihf target$(NC)\n" + cargo check --package aimdb-core --target thumbv7em-none-eabihf --no-default-features + @printf "$(YELLOW) → Checking aimdb-core (no_std/embassy) on thumbv7em-none-eabihf target$(NC)\n" + cargo check --package aimdb-core --target thumbv7em-none-eabihf --no-default-features --features "embassy-runtime" @printf "$(YELLOW) → Checking aimdb-embassy-adapter on thumbv7em-none-eabihf target$(NC)\n" cargo check --package aimdb-embassy-adapter --target thumbv7em-none-eabihf --features "embassy-runtime" test-feature-validation: @printf "$(BLUE)Testing feature flag validation (invalid combinations should fail)...$(NC)\n" - @printf "$(YELLOW) → Testing invalid combination: std + embedded$(NC)\n" - @! cargo build --package aimdb-core --features "std,embedded" 2>/dev/null || (echo "❌ Should have failed" && exit 1) + @printf "$(YELLOW) → Testing invalid combination: std + embassy-runtime$(NC)\n" + @! cargo build --package aimdb-core --features "std,embassy-runtime" 2>/dev/null || (echo "❌ Should have failed" && exit 1) @printf "$(GREEN) ✓ Correctly failed$(NC)\n" @printf "$(YELLOW) → Testing invalid combination: tokio-runtime + embassy-runtime$(NC)\n" @! cargo build --package aimdb-core --features "tokio-runtime,embassy-runtime" 2>/dev/null || (echo "❌ Should have failed" && exit 1) @printf "$(GREEN) ✓ Correctly failed$(NC)\n" - @printf "$(YELLOW) → Testing invalid combination: embedded + metrics$(NC)\n" - @! cargo build --package aimdb-core --features "embedded,metrics" 2>/dev/null || (echo "❌ Should have failed" && exit 1) + @printf "$(YELLOW) → Testing invalid combination: embassy-runtime + metrics (no_std conflict)$(NC)\n" + @! cargo build --package aimdb-core --no-default-features --features "embassy-runtime,metrics" 2>/dev/null || (echo "❌ Should have failed" && exit 1) @printf "$(GREEN) ✓ Correctly failed$(NC)\n" @printf "$(GREEN)All invalid combinations correctly rejected!$(NC)\n" +## Example projects +examples: + @printf "$(GREEN)Building all example projects...$(NC)\n" + @printf "$(YELLOW) → Building tokio-runtime-demo (native, tokio runtime)$(NC)\n" + cargo build --package aimdb-tokio-demo --features tokio-runtime + @printf "$(YELLOW) → Building producer-consumer-demo (native, tokio runtime)$(NC)\n" + cargo build --package producer-consumer-demo --features std + @printf "$(YELLOW) → Building embassy-runtime-demo (thumbv8m.main-none-eabihf, embassy runtime)$(NC)\n" + cargo build --package embassy-runtime-demo --target thumbv8m.main-none-eabihf --features embassy-runtime + @printf "$(GREEN)All examples built successfully!$(NC)\n" + ## Convenience commands check: fmt clippy test test-embedded test-feature-validation @printf "$(GREEN)Comprehensive development checks completed!$(NC)\n" @@ -124,5 +138,5 @@ check: fmt clippy test test-embedded test-feature-validation @printf "$(BLUE)✓ Embedded target compatibility verified$(NC)\n" @printf "$(BLUE)✓ Invalid feature combinations correctly rejected$(NC)\n" -all: build test +all: build test examples @printf "$(GREEN)Build and test completed!$(NC)\n" diff --git a/README.md b/README.md index ca072144..60deedf2 100644 --- a/README.md +++ b/README.md @@ -55,18 +55,47 @@ cd aimdb code . # Then: Ctrl/Cmd+Shift+P → "Dev Containers: Reopen in Container" -# 3. Inside the container, everything is ready: -cargo build --release -cargo run --example quickstart -p aimdb-examples +# 3. Inside the container, build and test: +make check # Format, lint, and test + +# 4. Run the examples: +cargo run --package tokio-runtime-demo # Std runtime +cargo run --package embassy-runtime-demo # Embedded (compile-only) ``` **✅ Zero Setup**: Rust, embedded targets and development tools pre-installed **✅ Cross-Platform**: Works on macOS, Linux, Windows (with Docker Desktop) or WSL **✅ VS Code Ready**: Optimized extensions and settings included -You should see a demo simulation showing the concept of data syncing between devices, edge, and cloud! +### Basic Usage + +```rust +use aimdb_core::{Database, RecordT}; +use aimdb_tokio_adapter::TokioAdapter; + +#[derive(Debug, Clone)] +struct SensorData(String); +impl RecordT for SensorData {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Build typed database + let db = Database::::builder() + .record::(&Default::default()) + .build()?; + + // Produce data + db.produce(SensorData("temp: 23.5°C".into())).await?; + + // Check stats + let stats = db.producer_stats::(); + println!("Calls: {}", stats.call_count()); + + Ok(()) +} +``` -> **💡 Note**: The current demo is a simple simulation. Real AimDB functionality is still being implemented as part of the early development process. +> **💡 Architecture**: Type-safe records with `TypeId`-based routing. No string keys, no macros. --- diff --git a/_external/embassy b/_external/embassy new file mode 160000 index 00000000..35b0ba4c --- /dev/null +++ b/_external/embassy @@ -0,0 +1 @@ +Subproject commit 35b0ba4ce0fed7588febe504e16bbf1788384f5a diff --git a/aimdb-core/Cargo.toml b/aimdb-core/Cargo.toml index 0e164da7..77ec38e3 100644 --- a/aimdb-core/Cargo.toml +++ b/aimdb-core/Cargo.toml @@ -7,32 +7,41 @@ description = "Core database engine for AimDB - async in-memory storage with rea build = "build.rs" [features] -default = [] +default = ["std"] -# Platform targets (mutually exclusive) -std = ["thiserror", "anyhow", "serde_json"] -embedded = [] +# Core capabilities +std = ["thiserror", "anyhow", "serde_json", "aimdb-executor/std"] -# Runtime adapters -tokio-runtime = ["tokio", "std"] -embassy-runtime = ["embassy-executor", "embedded"] +# Runtime adapters (imply capability requirements) +tokio-runtime = ["std", "tokio"] # Tokio requires std +embassy-runtime = ["embassy-executor"] # Embassy works in no_std -# Observability features (optional on both platforms) -tracing = [ - "dep:tracing", -] # Note: embedded targets may need no_std compatible tracing config -metrics = ["dep:metrics", "std"] +# Heap allocation in no_std environments +alloc = ["serde"] # Enable heap in no_std + +# Observability features (available on both std/no_std) +tracing = ["dep:tracing"] # Works in both std and no_std environments +metrics = ["dep:metrics", "std"] # Requires std for aggregation # Testing features test-utils = ["std"] [dependencies] +# Executor traits (always available) +aimdb-executor = { path = "../aimdb-executor", default-features = false } + +# Procedural macros (always available) +aimdb-macros = { path = "../aimdb-macros" } + +# Serialization (optional) +serde = { workspace = true, optional = true } + # Error handling - only for std environments thiserror = { workspace = true, optional = true } anyhow = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } -# Runtime dependencies (optional) +# Runtime support (optional) tokio = { workspace = true, optional = true } embassy-executor = { workspace = true, optional = true } @@ -40,6 +49,14 @@ embassy-executor = { workspace = true, optional = true } tracing = { workspace = true, optional = true } metrics = { workspace = true, optional = true } +# Synchronization primitives for no_std +spin = { version = "0.9", default-features = false, features = [ + "mutex", + "spin_mutex", +] } + [dev-dependencies] # For no_std testing heapless = "0.9.1" +# For async testing +tokio = { workspace = true, features = ["macros", "rt", "time"] } diff --git a/aimdb-core/build.rs b/aimdb-core/build.rs index 7b5fc6f6..a2083083 100644 --- a/aimdb-core/build.rs +++ b/aimdb-core/build.rs @@ -12,43 +12,17 @@ fn main() { // Get enabled features let std_enabled = env::var("CARGO_FEATURE_STD").is_ok(); - let embedded_enabled = env::var("CARGO_FEATURE_EMBEDDED").is_ok(); let tokio_runtime_enabled = env::var("CARGO_FEATURE_TOKIO_RUNTIME").is_ok(); let embassy_runtime_enabled = env::var("CARGO_FEATURE_EMBASSY_RUNTIME").is_ok(); let metrics_enabled = env::var("CARGO_FEATURE_METRICS").is_ok(); - // Validate platform feature combinations - if std_enabled && embedded_enabled { - panic!( - r#" -❌ Invalid feature combination: Cannot enable both 'std' and 'embedded' - - The 'std' and 'embedded' features are mutually exclusive platform targets. - - Valid combinations: - • std + tokio-runtime (Cloud/Edge deployment) - • embedded + embassy-runtime (MCU deployment) - • std (Basic edge device) - • embedded (Minimal MCU) - - For help: https://docs.aimdb.dev/features -"# - ); - } + // Note: no_std is the absence of std feature, no validation needed for mutual exclusion - // Validate runtime feature combinations + // Validate runtime feature combinations (skip validation when both are enabled via --all-features for testing) if tokio_runtime_enabled && embassy_runtime_enabled { - panic!( - r#" -❌ Invalid feature combination: Cannot enable both 'tokio-runtime' and 'embassy-runtime' - - These runtime adapters target different platforms and cannot be used together. - - Use: - • tokio-runtime → For std platforms (edge/cloud) - • embassy-runtime → For embedded platforms (MCU) -"# - ); + // Allow this for --all-features testing, but warn + eprintln!("⚠️ Warning: Both tokio-runtime and embassy-runtime enabled (likely from --all-features)"); + eprintln!(" This is only valid for testing. Production builds should use one runtime."); } // Validate metrics require std @@ -77,14 +51,15 @@ fn main() { ); } - if embassy_runtime_enabled && !embedded_enabled { + if embassy_runtime_enabled && std_enabled { panic!( r#" -❌ Invalid feature combination: 'embassy-runtime' requires 'embedded' platform +❌ Invalid feature combination: 'embassy-runtime' conflicts with 'std' - Embassy runtime is designed for no_std embedded environments. + Embassy runtime is designed for no_std environments. - Use: features = ["embedded", "embassy-runtime"] + Use: features = ["embassy-runtime"] (without std) + Or: features = ["embedded"] (convenience alias) "# ); } @@ -92,10 +67,8 @@ fn main() { // Set conditional compilation flags if std_enabled { println!("cargo:rustc-cfg=feature_std"); - } - - if embedded_enabled { - println!("cargo:rustc-cfg=feature_embedded"); + } else { + println!("cargo:rustc-cfg=feature_no_std"); } // Platform-specific optimizations diff --git a/aimdb-core/src/builder.rs b/aimdb-core/src/builder.rs new file mode 100644 index 00000000..9d916188 --- /dev/null +++ b/aimdb-core/src/builder.rs @@ -0,0 +1,477 @@ +//! Database builder with type-safe record registration +//! +//! Provides `AimDb` and `AimDbBuilder` for constructing databases with +//! type-safe, self-registering records using the producer-consumer pattern. + +use core::any::TypeId; +use core::fmt::Debug; + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, sync::Arc, vec::Vec}; + +#[cfg(feature = "std")] +use std::{boxed::Box, sync::Arc, vec::Vec}; + +#[cfg(not(feature = "std"))] +use alloc::collections::BTreeMap; + +#[cfg(feature = "std")] +use std::collections::HashMap; + +use crate::emitter::Emitter; +use crate::producer_consumer::{RecordRegistrar, RecordT}; +use crate::typed_record::{AnyRecord, AnyRecordExt, TypedRecord}; +use crate::DbResult; + +/// Internal database state +/// +/// Holds the registry of typed records, indexed by `TypeId`. +pub struct AimDbInner { + /// Map from TypeId to type-erased records + #[cfg(feature = "std")] + pub(crate) records: HashMap>, + + #[cfg(not(feature = "std"))] + pub(crate) records: BTreeMap>, +} + +/// Database builder for producer-consumer pattern +/// +/// Provides a fluent API for constructing databases with type-safe +/// record registration. +/// +/// # Design Philosophy +/// +/// - **Type Safety**: Records are identified by TypeId, not strings +/// - **Validation**: Ensures all records have valid producer/consumer setup +/// - **Runtime Agnostic**: Works with any Runtime implementation +/// - **Builder Pattern**: Fluent API for construction +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::experimental::AimDb; +/// use aimdb_tokio_adapter::TokioAdapter; +/// +/// let runtime = Arc::new(TokioAdapter::new()?); +/// +/// let db = AimDb::build_with(runtime, |b| { +/// b.register_record::(&sensor_cfg); +/// b.register_record::(&alert_cfg); +/// })?; +/// ``` +pub struct AimDbBuilder { + /// Registry of typed records + #[cfg(feature = "std")] + records: HashMap>, + + #[cfg(not(feature = "std"))] + records: BTreeMap>, + + /// Runtime adapter (type-erased for storage) + runtime: Option>, +} + +impl AimDbBuilder { + /// Creates a new database builder + /// + /// # Returns + /// An empty `AimDbBuilder` + pub fn new() -> Self { + Self { + #[cfg(feature = "std")] + records: HashMap::new(), + #[cfg(not(feature = "std"))] + records: BTreeMap::new(), + runtime: None, + } + } + + /// Sets the runtime adapter + /// + /// # Arguments + /// * `rt` - The runtime adapter to use + /// + /// # Returns + /// `Self` for method chaining + /// + /// # Example + /// + /// ```rust,ignore + /// let builder = AimDbBuilder::new() + /// .with_runtime(Arc::new(TokioAdapter::new()?)); + /// ``` + pub fn with_runtime(mut self, rt: Arc) -> Self + where + R: 'static + Send + Sync, + { + self.runtime = Some(rt); + self + } + + /// Configures a record type manually + /// + /// This is a low-level method for advanced use cases. Most users + /// should use `register_record` instead. + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Arguments + /// * `f` - A function that configures the record via `RecordRegistrar` + /// + /// # Returns + /// `&mut Self` for method chaining + /// + /// # Example + /// + /// ```rust,ignore + /// builder.configure::(|reg| { + /// reg.producer(|em, data| async move { + /// println!("Sensor: {:?}", data); + /// }) + /// .consumer(|em, data| async move { + /// println!("Consumer: {:?}", data); + /// }); + /// }); + /// ``` + pub fn configure( + &mut self, + f: impl for<'a> FnOnce(&'a mut RecordRegistrar<'a, T>), + ) -> &mut Self + where + T: Send + 'static + Debug + Clone, + { + let entry = self + .records + .entry(TypeId::of::()) + .or_insert_with(|| Box::new(TypedRecord::::new())); + + let rec = entry + .as_typed_mut::() + .expect("type mismatch in record registry"); + + let mut reg = RecordRegistrar { rec }; + f(&mut reg); + self + } + + /// Registers a self-registering record type + /// + /// This is the primary method for adding records to the database. + /// The record type must implement `RecordT`. + /// + /// # Type Parameters + /// * `R` - The record type implementing `RecordT` + /// + /// # Arguments + /// * `cfg` - Configuration for the record + /// + /// # Returns + /// `&mut Self` for method chaining + /// + /// # Example + /// + /// ```rust,ignore + /// let sensor_cfg = SensorConfig { threshold: 100.0 }; + /// builder.register_record::(&sensor_cfg); + /// ``` + pub fn register_record(&mut self, cfg: &R::Config) -> &mut Self + where + R: RecordT, + { + self.configure::(|reg| R::register(reg, cfg)) + } + + /// Builds the database + /// + /// Validates that all records have proper producer/consumer setup + /// and constructs the final `AimDb` instance. + /// + /// # Returns + /// `Ok(AimDb)` if successful, `Err` if validation fails or runtime not set + /// + /// # Errors + /// - Runtime not set (use `with_runtime`) + /// - Record validation failed (missing producer or consumers) + /// + /// # Example + /// + /// ```rust,ignore + /// let db = builder.build()?; + /// ``` + pub fn build(self) -> DbResult { + use crate::DbError; + + // Validate all records + for record in self.records.values() { + record.validate().map_err(|_msg| { + #[cfg(feature = "std")] + { + DbError::RuntimeError { + message: format!("Record validation failed: {}", _msg), + } + } + #[cfg(not(feature = "std"))] + { + DbError::RuntimeError { _message: () } + } + })?; + } + + // Ensure runtime is set + let runtime = self.runtime.ok_or({ + #[cfg(feature = "std")] + { + DbError::RuntimeError { + message: "runtime not set (use with_runtime)".into(), + } + } + #[cfg(not(feature = "std"))] + { + DbError::RuntimeError { _message: () } + } + })?; + + let inner = Arc::new(AimDbInner { + records: self.records, + }); + + Ok(AimDb { inner, runtime }) + } +} + +impl Default for AimDbBuilder { + fn default() -> Self { + Self::new() + } +} + +/// Producer-consumer database +/// +/// A database instance with type-safe record registration and +/// cross-record communication via the Emitter pattern. +/// +/// # Design Philosophy +/// +/// - **Type Safety**: Records identified by type, not strings +/// - **Reactive**: Data flows through producer-consumer pipelines +/// - **Observable**: Built-in call tracking and statistics +/// - **Runtime Agnostic**: Works with any Runtime implementation +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::experimental::AimDb; +/// +/// // Build database +/// let db = AimDb::build_with(runtime, |b| { +/// b.register_record::(&sensor_cfg); +/// b.register_record::(&alert_cfg); +/// })?; +/// +/// // Produce data - flows through pipeline +/// db.produce(SensorData { temp: 75.0 }).await?; +/// +/// // Inspect statistics +/// if let Some((calls, last)) = db.producer_stats::() { +/// println!("Producer called {} times", calls); +/// } +/// ``` +pub struct AimDb { + /// Internal state + inner: Arc, + + /// Runtime adapter (type-erased) + runtime: Arc, +} + +impl AimDb { + /// Builds a database with a closure-based builder pattern + /// + /// This is the primary construction method for most use cases. + /// + /// # Arguments + /// * `rt` - The runtime adapter to use + /// * `f` - A closure that configures the builder + /// + /// # Returns + /// `Ok(AimDb)` if successful, `Err` if validation fails + /// + /// # Example + /// + /// ```rust,ignore + /// let db = AimDb::build_with(runtime, |b| { + /// b.register_record::(&sensor_cfg); + /// b.register_record::(&alert_cfg); + /// })?; + /// ``` + pub fn build_with(rt: Arc, f: impl FnOnce(&mut AimDbBuilder)) -> DbResult + where + R: 'static + Send + Sync, + { + let mut b = AimDbBuilder::new().with_runtime(rt); + f(&mut b); + b.build() + } + + /// Returns an emitter for cross-record communication + /// + /// The emitter can be cloned and passed to async tasks. + /// + /// # Returns + /// An `Emitter` instance + /// + /// # Example + /// + /// ```rust,ignore + /// let emitter = db.emitter(); + /// emitter.emit(Alert::new("Test")).await?; + /// ``` + pub fn emitter(&self) -> Emitter { + // Clone the Arc to get a new reference + let runtime_clone = self.runtime.clone(); + // Create emitter with the cloned runtime (already type-erased) + Emitter { + runtime: runtime_clone, + inner: self.inner.clone(), + } + } + + /// Produces a value for a record type + /// + /// This triggers the producer and all consumers for the given type. + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Arguments + /// * `value` - The value to produce + /// + /// # Returns + /// `Ok(())` if successful, `Err` if record type not registered + /// + /// # Example + /// + /// ```rust,ignore + /// db.produce(SensorData { temp: 23.5 }).await?; + /// ``` + pub async fn produce(&self, value: T) -> DbResult<()> + where + T: Send + 'static + Debug + Clone, + { + self.emitter().emit::(value).await + } + + /// Returns producer statistics for a record type + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Returns + /// `Some((calls, last_value))` if record exists and has a producer, `None` otherwise + /// + /// # Example + /// + /// ```rust,ignore + /// if let Some((calls, last)) = db.producer_stats::() { + /// println!("Producer called {} times, last value: {:?}", calls, last); + /// } + /// ``` + pub fn producer_stats(&self) -> Option<(u64, Option)> + where + T: Send + 'static + Debug + Clone, + { + let rec = self.inner.records.get(&TypeId::of::())?; + let rec_typed = rec.as_typed::()?; + let stats = rec_typed.producer_stats()?; + Some((stats.calls(), stats.last_arg())) + } + + /// Returns consumer statistics for a record type + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Returns + /// A vector of `(calls, last_value)` tuples, one per consumer + /// + /// # Example + /// + /// ```rust,ignore + /// for (i, (calls, last)) in db.consumer_stats::().into_iter().enumerate() { + /// println!("Consumer {} called {} times", i, calls); + /// } + /// ``` + pub fn consumer_stats(&self) -> Vec<(u64, Option)> + where + T: Send + 'static + Debug + Clone, + { + let Some(rec) = self.inner.records.get(&TypeId::of::()) else { + return Vec::new(); + }; + let Some(rec_typed) = rec.as_typed::() else { + return Vec::new(); + }; + rec_typed + .consumer_stats() + .into_iter() + .map(|s| (s.calls(), s.last_arg())) + .collect() + } + + /// Returns a reference to the runtime (downcasted) + /// + /// # Type Parameters + /// * `R` - The expected runtime type + /// + /// # Returns + /// `Some(&R)` if the runtime type matches, `None` otherwise + pub fn runtime(&self) -> Option<&R> { + self.runtime.downcast_ref::() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, Clone, PartialEq)] + struct TestData { + value: i32, + } + + struct TestConfig; + + impl RecordT for TestData { + type Config = TestConfig; + + fn register<'a>(reg: &'a mut RecordRegistrar<'a, Self>, _cfg: &Self::Config) { + reg.producer(|_em, _data| async {}) + .consumer(|_em, _data| async {}); + } + } + + #[test] + fn test_builder_basic() { + let mut builder = AimDbBuilder::new(); + builder.register_record::(&TestConfig); + + // Should have one record registered + assert_eq!(builder.records.len(), 1); + } + + #[test] + fn test_builder_configure() { + let mut builder = AimDbBuilder::new(); + builder.configure::(|reg| { + reg.producer(|_em, _data| async {}) + .consumer(|_em, _data| async {}); + }); + + assert_eq!(builder.records.len(), 1); + } +} diff --git a/aimdb-core/src/context.rs b/aimdb-core/src/context.rs new file mode 100644 index 00000000..9e26cf0e --- /dev/null +++ b/aimdb-core/src/context.rs @@ -0,0 +1,397 @@ +//! Runtime context for AimDB services +//! +//! Provides a unified interface to runtime capabilities like sleep and timestamp +//! functions, abstracting away the specific runtime adapter implementation. + +use aimdb_executor::Runtime; +use core::future::Future; + +use crate::emitter::Emitter; + +/// Unified runtime context for AimDB services +/// +/// This context provides access to essential runtime capabilities through +/// a clean, unified API. Services receive this context and can use it for +/// timing operations without needing to know about the underlying runtime. +/// +/// The context holds a reference or smart pointer to the runtime, enabling +/// efficient sharing across service instances without requiring cloning. +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::{RuntimeContext, service, DbResult}; +/// use aimdb_tokio_adapter::TokioAdapter; +/// use std::time::Duration; +/// +/// #[service] +/// async fn my_service(ctx: RuntimeContext) -> DbResult<()> { +/// println!("Service starting at: {:?}", ctx.now()); +/// +/// // Sleep using the runtime's sleep capability +/// ctx.sleep(Duration::from_millis(100)).await; +/// +/// println!("Service completed at: {:?}", ctx.now()); +/// Ok(()) +/// } +/// ``` +#[derive(Clone)] +pub struct RuntimeContext +where + R: Runtime, +{ + #[cfg(feature = "std")] + runtime: std::sync::Arc, + #[cfg(not(feature = "std"))] + runtime: &'static R, + /// Optional emitter for cross-record communication + #[cfg(feature = "std")] + emitter: Option, + #[cfg(not(feature = "std"))] + emitter: Option, +} + +#[cfg(feature = "std")] +impl RuntimeContext +where + R: Runtime, +{ + /// Create a new RuntimeContext with the given runtime adapter (std version) + /// + /// In std environments, the runtime is wrapped in an Arc for efficient sharing. + /// + /// # Arguments + /// + /// * `runtime` - Runtime adapter implementing the Runtime trait + pub fn new(runtime: R) -> Self { + Self { + runtime: std::sync::Arc::new(runtime), + emitter: None, + } + } + + /// Create a RuntimeContext from an Arc (for efficiency) + /// + /// This avoids double-wrapping when you already have an Arc. + pub fn from_arc(runtime: std::sync::Arc) -> Self { + Self { + runtime, + emitter: None, + } + } + + /// Create a RuntimeContext with an emitter + /// + /// This allows services to emit data to typed records. + pub fn with_emitter(mut self, emitter: Emitter) -> Self { + self.emitter = Some(emitter); + self + } +} + +#[cfg(not(feature = "std"))] +impl RuntimeContext +where + R: Runtime, +{ + /// Create a new RuntimeContext with a static reference (no_std version) + /// + /// In no_std environments, requires a static reference since we can't use Arc. + /// + /// # Arguments + /// + /// * `runtime` - Static reference to runtime adapter + pub fn new(runtime: &'static R) -> Self { + Self { + runtime, + emitter: None, + } + } + + /// Create a RuntimeContext with an emitter (no_std version) + /// + /// This allows services to emit data to typed records. + pub fn with_emitter(mut self, emitter: Emitter) -> Self { + self.emitter = Some(emitter); + self + } +} + +impl RuntimeContext +where + R: Runtime, +{ + /// Access time utilities + /// + /// Returns a time accessor that provides duration creation, sleep, and timing operations. + /// + /// # Example + /// + /// ```rust,ignore + /// # use aimdb_core::RuntimeContext; + /// # use aimdb_tokio_adapter::TokioAdapter; + /// # async fn example(ctx: &RuntimeContext) { + /// // Get time accessor + /// let time = ctx.time(); + /// + /// // Use it for various operations + /// let start = time.now(); + /// time.sleep(time.millis(500)).await; + /// let elapsed = time.duration_since(time.now(), start); + /// # } + /// ``` + pub fn time(&self) -> Time<'_, R> { + Time { ctx: self } + } + + /// Access logging utilities + /// + /// Returns a logger accessor that provides structured logging operations. + /// + /// # Example + /// + /// ```rust,ignore + /// # use aimdb_core::RuntimeContext; + /// # use aimdb_tokio_adapter::TokioAdapter; + /// # fn example(ctx: &RuntimeContext) { + /// ctx.log().info("Service started"); + /// ctx.log().warn("High memory usage"); + /// ctx.log().error("Connection failed"); + /// # } + /// ``` + pub fn log(&self) -> Log<'_, R> { + Log { ctx: self } + } + + /// Access the emitter for cross-record communication + /// + /// Returns the emitter if one was configured, allowing services to emit + /// data to typed records in the producer-consumer pipeline. + /// + /// # Example + /// + /// ```rust,ignore + /// # use aimdb_core::RuntimeContext; + /// # use aimdb_tokio_adapter::TokioAdapter; + /// # async fn example(ctx: &RuntimeContext) { + /// if let Some(emitter) = ctx.emitter() { + /// emitter.emit(MyRecord::new("data")).await; + /// } + /// # } + /// ``` + pub fn emitter(&self) -> Option<&Emitter> { + self.emitter.as_ref() + } + + /// Get access to the underlying runtime + /// + /// This provides direct access to the runtime for advanced use cases. + #[cfg(feature = "std")] + pub fn runtime(&self) -> &R { + &self.runtime + } + + #[cfg(not(feature = "std"))] + pub fn runtime(&self) -> &'static R { + self.runtime + } +} + +#[cfg(feature = "std")] +impl RuntimeContext +where + R: Runtime, +{ + /// Create a RuntimeContext from a runtime adapter (std version with Arc) + /// + /// This is a convenience method that wraps the runtime in an Arc. + /// + /// # Arguments + /// + /// * `runtime` - Runtime adapter implementing the Runtime trait + pub fn from_runtime(runtime: R) -> Self { + Self::new(runtime) + } +} + +/// Create a RuntimeContext from any type that implements the Runtime trait (std version) +/// +/// This function provides a generic way to create a RuntimeContext from any +/// runtime adapter, as long as it implements the Runtime trait. +/// This is particularly useful in macros where we don't know the concrete type. +/// +/// # Arguments +/// * `runtime` - Any type implementing Runtime +/// +/// # Returns +/// A RuntimeContext wrapping the provided runtime in an Arc +#[cfg(feature = "std")] +pub fn create_runtime_context(runtime: R) -> RuntimeContext +where + R: Runtime, +{ + RuntimeContext::from_runtime(runtime) +} + +/// Time utilities accessor for RuntimeContext +/// +/// This accessor provides all time-related operations including duration creation, +/// sleep, and timing measurements. It encapsulates time functionality separately +/// from other context capabilities like logging. +/// +/// # Design Philosophy +/// +/// - **Separation of Concerns**: Time operations are isolated from logging and other capabilities +/// - **Clear Intent**: `ctx.time().sleep()` clearly indicates time-based operation +/// - **Extensible**: Easy to add new time-related methods without cluttering RuntimeContext +/// +/// # Example +/// +/// ```rust,ignore +/// async fn my_service(ctx: RuntimeContext) { +/// let time = ctx.time(); +/// +/// let start = time.now(); +/// time.sleep(time.millis(100)).await; +/// let elapsed = time.duration_since(time.now(), start); +/// } +/// ``` +pub struct Time<'a, R: Runtime> { + ctx: &'a RuntimeContext, +} + +impl<'a, R: Runtime> Time<'a, R> { + /// Create a duration from milliseconds + /// + /// # Example + /// ```rust,ignore + /// time.sleep(time.millis(500)).await; + /// ``` + pub fn millis(&self, millis: u64) -> R::Duration { + self.ctx.runtime.millis(millis) + } + + /// Create a duration from seconds + /// + /// # Example + /// ```rust,ignore + /// time.sleep(time.secs(2)).await; + /// ``` + pub fn secs(&self, secs: u64) -> R::Duration { + self.ctx.runtime.secs(secs) + } + + /// Create a duration from microseconds + /// + /// # Example + /// ```rust,ignore + /// time.sleep(time.micros(1000)).await; + /// ``` + pub fn micros(&self, micros: u64) -> R::Duration { + self.ctx.runtime.micros(micros) + } + + /// Sleep for the specified duration + /// + /// # Example + /// ```rust,ignore + /// time.sleep(time.millis(100)).await; + /// ``` + pub fn sleep(&self, duration: R::Duration) -> impl Future + Send + '_ { + self.ctx.runtime.sleep(duration) + } + + /// Get the current timestamp + /// + /// # Example + /// ```rust,ignore + /// let now = time.now(); + /// ``` + pub fn now(&self) -> R::Instant { + self.ctx.runtime.now() + } + + /// Get the duration between two instants + /// + /// Returns None if `later` is before `earlier` + /// + /// # Example + /// ```rust,ignore + /// let start = time.now(); + /// // ... do work ... + /// let end = time.now(); + /// let elapsed = time.duration_since(end, start); + /// ``` + pub fn duration_since(&self, later: R::Instant, earlier: R::Instant) -> Option { + self.ctx.runtime.duration_since(later, earlier) + } +} + +/// Logging accessor for RuntimeContext +/// +/// This accessor provides all logging operations, separating them from time and +/// other context capabilities for better organization and testability. +/// +/// # Design Philosophy +/// +/// - **Separation of Concerns**: Logging is isolated from time and other operations +/// - **Clear Intent**: `ctx.log().info()` clearly indicates logging operation +/// - **Mockable**: Easy to mock logging separately from other capabilities +/// +/// # Example +/// +/// ```rust,ignore +/// async fn my_service(ctx: RuntimeContext) { +/// let log = ctx.log(); +/// +/// log.info("Service starting"); +/// log.debug("Debug information"); +/// log.warn("Warning message"); +/// log.error("Error occurred"); +/// } +/// ``` +pub struct Log<'a, R: Runtime> { + ctx: &'a RuntimeContext, +} + +impl<'a, R: Runtime> Log<'a, R> { + /// Log an informational message + /// + /// # Example + /// ```rust,ignore + /// ctx.log().info("Service started"); + /// ``` + pub fn info(&self, message: &str) { + self.ctx.runtime.info(message) + } + + /// Log a debug message + /// + /// # Example + /// ```rust,ignore + /// ctx.log().debug("Processing item"); + /// ``` + pub fn debug(&self, message: &str) { + self.ctx.runtime.debug(message) + } + + /// Log a warning message + /// + /// # Example + /// ```rust,ignore + /// ctx.log().warn("High memory usage"); + /// ``` + pub fn warn(&self, message: &str) { + self.ctx.runtime.warn(message) + } + + /// Log an error message + /// + /// # Example + /// ```rust,ignore + /// ctx.log().error("Connection failed"); + /// ``` + pub fn error(&self, message: &str) { + self.ctx.runtime.error(message) + } +} diff --git a/aimdb-core/src/database.rs b/aimdb-core/src/database.rs new file mode 100644 index 00000000..f285336d --- /dev/null +++ b/aimdb-core/src/database.rs @@ -0,0 +1,352 @@ +//! AimDB Database Implementation +//! +//! This module provides the unified database implementation for AimDB, supporting async +//! in-memory storage with type-safe records and real-time synchronization across +//! MCU → edge → cloud environments. + +use crate::{ + AimDb, AimDbBuilder, DbError, DbResult, Emitter, RecordT, RuntimeAdapter, RuntimeContext, +}; +use aimdb_executor::Spawn; +use core::fmt::Debug; + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{sync::Arc, vec::Vec}; + +#[cfg(feature = "std")] +use std::{sync::Arc, vec::Vec}; + +/// Database specification +/// +/// This struct holds the configuration for creating a database instance. +/// It is runtime-agnostic and works with any RuntimeAdapter implementation. +#[allow(dead_code)] // Used by adapter crates +pub struct DatabaseSpec { + pub(crate) aimdb_builder: AimDbBuilder, + _phantom: core::marker::PhantomData, +} + +impl DatabaseSpec { + /// Creates a new specification builder + /// + /// # Returns + /// A builder for constructing database specifications + pub fn builder() -> DatabaseSpecBuilder { + DatabaseSpecBuilder::new() + } +} + +/// Builder for database specifications +/// +/// This builder provides a fluent API for configuring databases with +/// type-safe record registration. +pub struct DatabaseSpecBuilder { + aimdb_builder: AimDbBuilder, + _phantom: core::marker::PhantomData, +} + +impl DatabaseSpecBuilder { + /// Creates a new database specification builder + fn new() -> Self { + Self { + aimdb_builder: AimDbBuilder::new(), + _phantom: core::marker::PhantomData, + } + } + + /// Registers a type-safe record with configuration + /// + /// # Type Parameters + /// * `T` - The record type implementing `RecordT` + /// + /// # Arguments + /// * `cfg` - Configuration for the record + /// + /// # Returns + /// Self for method chaining + /// + /// # Example + /// ```rust,ignore + /// let builder = Database::::builder() + /// .record::(&sensor_cfg) + /// .record::(&alert_cfg); + /// ``` + pub fn record(mut self, cfg: &T::Config) -> Self { + #[cfg(feature = "tracing")] + tracing::debug!("Registering typed record: {}", core::any::type_name::()); + + self.aimdb_builder.register_record::(cfg); + self + } + + /// Internal method to convert builder to spec + /// + /// This is used by adapter-specific build methods. + #[allow(dead_code)] // Used by adapter crates + pub fn into_spec(self) -> DatabaseSpec { + #[cfg(feature = "tracing")] + tracing::info!("Building database spec with typed records"); + + DatabaseSpec { + aimdb_builder: self.aimdb_builder, + _phantom: core::marker::PhantomData, + } + } +} + +/// AimDB Database implementation +/// +/// Provides a unified database implementation combining runtime adapter management +/// with type-safe record registration and producer-consumer patterns. +/// +/// # Design Philosophy +/// +/// - **Type Safety**: Records identified by type, not strings +/// - **Runtime Agnostic**: Core behavior doesn't depend on specific runtimes +/// - **Async First**: All operations are async for consistency +/// - **Reactive Data Flow**: Producer-consumer pipelines with observability +/// - **Service Orchestration**: Manages spawned services on the runtime +/// +/// See the repository examples for complete usage patterns. +pub struct Database { + adapter: A, + aimdb: AimDb, +} + +impl Database { + /// Creates a new database builder + /// + /// Use this to start configuring a database with type-safe records. + /// The builder provides runtime-specific build methods in the adapter crates. + /// + /// See the repository examples for complete usage. + pub fn builder() -> DatabaseSpecBuilder { + DatabaseSpecBuilder::new() + } + + /// Internal constructor for creating a database from a spec + /// + /// This is called by adapter-specific build methods and should not + /// be used directly. + #[allow(dead_code)] // Used by adapter crates + pub fn new(adapter: A, spec: DatabaseSpec) -> DbResult + where + A: Clone + 'static, + { + #[cfg(feature = "tracing")] + tracing::info!("Initializing unified database with typed records"); + + // Build the AimDb with the runtime + let runtime = Arc::new(adapter.clone()); + let aimdb = spec.aimdb_builder.with_runtime(runtime).build()?; + + Ok(Self { adapter, aimdb }) + } + + /// Gets a reference to the runtime adapter + /// + /// This allows direct access to the runtime adapter for advanced use cases. + /// + /// # Example + /// ```rust,ignore + /// # use aimdb_core::Database; + /// # #[cfg(feature = "tokio-runtime")] + /// # { + /// # async fn example(db: Database) { + /// let adapter = db.adapter(); + /// // Use adapter directly + /// # } + /// # } + /// ``` + pub fn adapter(&self) -> &A { + &self.adapter + } + + /// Gets the emitter for cross-record communication + /// + /// The emitter allows producing data to typed records from anywhere + /// in your application. + /// + /// # Returns + /// An `Emitter` for cross-record communication + /// + /// # Example + /// ```rust,ignore + /// # async fn example(db: aimdb_core::Database) { + /// let emitter = db.emitter(); + /// emitter.emit(SensorData { temp: 23.5 }).await; + /// # } + /// ``` + pub fn emitter(&self) -> Emitter { + self.aimdb.emitter() + } + + /// Produces typed data to the record's producer pipeline + /// + /// This is the primary way to inject data into the reactive pipeline. + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Arguments + /// * `data` - The data to produce + /// + /// # Returns + /// `DbResult<()>` indicating success or failure + /// + /// # Example + /// ```rust,ignore + /// # async fn example(db: aimdb_core::Database) -> aimdb_core::DbResult<()> { + /// db.produce(SensorData { temp: 23.5 }).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn produce(&self, data: T) -> DbResult<()> + where + T: Send + 'static + Clone + core::fmt::Debug, + { + self.aimdb.produce(data).await + } + + /// Gets producer call statistics for a record type + /// + /// Returns the number of times the producer was called and the last value. + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Returns + /// `Option<(u64, Option)>` with call count and last value + /// + /// # Example + /// ```rust,ignore + /// # fn example(db: aimdb_core::Database) { + /// if let Some((calls, last)) = db.producer_stats::() { + /// println!("Producer called {} times", calls); + /// } + /// # } + /// ``` + pub fn producer_stats(&self) -> Option<(u64, Option)> + where + T: Send + 'static + Debug + Clone, + { + self.aimdb.producer_stats() + } + + /// Gets consumer call statistics for a record type + /// + /// Returns statistics for all consumers registered on this record type. + /// Returns an empty vector if the record type has no consumers registered. + /// + /// # Type Parameters + /// * `T` - The record type + /// + /// # Returns + /// `Vec<(u64, Option)>` with stats for each consumer + /// + /// # Example + /// ```rust,ignore + /// # fn example(db: aimdb_core::Database) { + /// let stats = db.consumer_stats::(); + /// for (i, (calls, last)) in stats.iter().enumerate() { + /// println!("Consumer {} called {} times", i, calls); + /// } + /// # } + /// ``` + pub fn consumer_stats(&self) -> Vec<(u64, Option)> + where + T: Send + 'static + Debug + Clone, + { + self.aimdb.consumer_stats() + } + + /// Creates a RuntimeContext for this database + /// + /// The context provides services with access to runtime capabilities + /// like timing and logging, plus the emitter for cross-record communication. + /// + /// # Returns + /// A RuntimeContext configured for this database's runtime with emitter included + /// + /// # Example + /// ```rust,ignore + /// # use aimdb_core::Database; + /// # #[cfg(feature = "tokio-runtime")] + /// # { + /// # async fn example(db: Database) { + /// let ctx = db.context(); + /// // Pass ctx to services - they can use ctx.emitter() + /// # } + /// # } + /// ``` + pub fn context(&self) -> RuntimeContext + where + A: aimdb_executor::Runtime + Clone, + { + #[cfg(feature = "std")] + { + RuntimeContext::from_arc(std::sync::Arc::new(self.adapter.clone())) + .with_emitter(self.emitter()) + } + #[cfg(not(feature = "std"))] + { + // For no_std, we need a static reference - this would typically be handled + // by the caller storing the adapter in a static cell first + // For now, we'll document this limitation + panic!("context() not supported in no_std without a static reference. To use context(), store your adapter in a static cell (e.g., StaticCell from portable-atomic or embassy-sync), or use adapter() directly.") + } + } +} + +// Spawn implementation for databases with spawn-capable adapters +impl Database +where + A: RuntimeAdapter + Spawn, +{ + /// Spawns a service on the database's runtime + /// + /// This method provides a unified interface for spawning services + /// across different runtime adapters. + /// + /// # Arguments + /// * `future` - The service future to spawn + /// + /// # Returns + /// `DbResult<()>` indicating whether the spawn succeeded + /// + /// # Example + /// ```rust,ignore + /// # use aimdb_core::Database; + /// # use aimdb_executor::{Runtime, Spawn}; + /// # #[cfg(feature = "tokio-runtime")] + /// # { + /// async fn my_service(ctx: aimdb_core::RuntimeContext) -> aimdb_core::DbResult<()> { + /// // Service implementation + /// Ok(()) + /// } + /// + /// # async fn example(db: Database) -> aimdb_core::DbResult<()> { + /// let ctx = db.context(); + /// db.spawn(async move { + /// if let Err(e) = my_service(ctx).await { + /// eprintln!("Service error: {:?}", e); + /// } + /// })?; + /// # Ok(()) + /// # } + /// # } + /// ``` + pub fn spawn(&self, future: F) -> DbResult<()> + where + F: core::future::Future + Send + 'static, + { + #[cfg(feature = "tracing")] + tracing::debug!("Spawning service on database runtime"); + + self.adapter.spawn(future).map_err(DbError::from)?; + Ok(()) + } +} diff --git a/aimdb-core/src/emitter.rs b/aimdb-core/src/emitter.rs new file mode 100644 index 00000000..2c463888 --- /dev/null +++ b/aimdb-core/src/emitter.rs @@ -0,0 +1,212 @@ +//! Cross-record communication via Emitter pattern +//! +//! The Emitter provides a way for records to emit data to other record types, +//! enabling reactive data flow pipelines across the database. + +use core::any::TypeId; +use core::fmt::Debug; +use core::future::Future; +use core::pin::Pin; + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, sync::Arc}; + +#[cfg(feature = "std")] +use std::{boxed::Box, sync::Arc}; + +use crate::DbResult; + +// Forward declare AimDbInner (will be defined in builder.rs) +pub use crate::builder::AimDbInner; + +/// Type alias for boxed future returning unit +#[allow(dead_code)] +type BoxFutureUnit = Pin + Send + 'static>>; + +/// Emitter for cross-record communication +/// +/// The Emitter allows records to emit data to other record types, +/// creating reactive data flow pipelines. It is generic over the runtime type. +/// +/// # Design Philosophy +/// +/// - **Runtime Generic**: Works with any runtime implementing `Runtime` +/// - **Type Safety**: Emit operations are type-checked at compile time +/// - **Cross-Record Flow**: Records can emit to any other registered record type +/// - **Clone-able**: Can be cloned cheaply (Arc-based) for passing to async tasks +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::experimental::Emitter; +/// +/// async fn process_sensor(emitter: Emitter, data: SensorData) { +/// // Do processing +/// let processed = process(data); +/// +/// // Emit to another record type if threshold exceeded +/// if processed.value > 100.0 { +/// emitter.emit(Alert::new("High value detected")).await?; +/// } +/// } +/// ``` +#[derive(Clone)] +pub struct Emitter { + /// Runtime adapter (type-erased for storage) + #[cfg(feature = "std")] + pub(crate) runtime: Arc, + + #[cfg(not(feature = "std"))] + pub(crate) runtime: Arc, + + /// Database internal state (record registry) + pub(crate) inner: Arc, +} + +impl Emitter { + /// Creates a new emitter from a concrete runtime + /// + /// # Arguments + /// * `runtime` - The runtime adapter + /// * `inner` - The database internal state + /// + /// # Returns + /// A new `Emitter` instance + pub fn new(runtime: Arc, inner: Arc) -> Self + where + R: 'static + Send + Sync, + { + Self { + runtime: runtime as Arc, + inner, + } + } + + /// Gets a reference to the runtime (downcasted) + /// + /// # Type Parameters + /// * `R` - The expected runtime type + /// + /// # Returns + /// `Some(&R)` if the runtime type matches, `None` otherwise + pub fn runtime(&self) -> Option<&R> { + self.runtime.downcast_ref::() + } + + /// Emits a value to another record type + /// + /// This is the core cross-record communication primitive. When called, + /// it will invoke the producer and all consumers registered for the + /// target record type. + /// + /// # Type Parameters + /// * `U` - The target record type to emit to + /// + /// # Arguments + /// * `value` - The value to emit + /// + /// # Returns + /// `Ok(())` if successful, `Err` if the record type is not registered + /// + /// # Example + /// + /// ```rust,ignore + /// // In a producer or consumer function + /// async fn process(emitter: Emitter, data: SensorData) { + /// if data.temp > 100.0 { + /// // Emit to Alert record type + /// emitter.emit(Alert::new("Temperature too high")).await?; + /// } + /// } + /// ``` + pub async fn emit(&self, value: U) -> DbResult<()> + where + U: Send + 'static + Debug + Clone, + { + use crate::typed_record::AnyRecordExt; + use crate::DbError; + + // Look up the record by TypeId + let rec = self.inner.records.get(&TypeId::of::()).ok_or({ + #[cfg(feature = "std")] + { + DbError::RuntimeError { + message: format!( + "No record registered for type: {:?}", + core::any::type_name::() + ), + } + } + #[cfg(not(feature = "std"))] + { + DbError::RuntimeError { _message: () } + } + })?; + + // Downcast to typed record + let rec_typed = rec.as_typed::().ok_or({ + #[cfg(feature = "std")] + { + DbError::RuntimeError { + message: "Type mismatch in record registry".into(), + } + } + #[cfg(not(feature = "std"))] + { + DbError::RuntimeError { _message: () } + } + })?; + + // Produce the value (calls producer and consumers) + rec_typed.produce(self.clone(), value).await; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(not(feature = "std"))] + use alloc::collections::BTreeMap; + #[cfg(feature = "std")] + use std::collections::HashMap; + + #[allow(dead_code)] + #[derive(Debug, Clone, PartialEq)] + struct TestData { + value: i32, + } + + #[test] + fn test_emitter_creation() { + #[cfg(feature = "std")] + let records = HashMap::new(); + #[cfg(not(feature = "std"))] + let records = BTreeMap::new(); + + let inner = Arc::new(AimDbInner { records }); + + // Create with a dummy runtime + let runtime = Arc::new(()); + let _emitter = Emitter::new(runtime, inner); + } + + #[test] + fn test_emitter_clone() { + #[cfg(feature = "std")] + let records = HashMap::new(); + #[cfg(not(feature = "std"))] + let records = BTreeMap::new(); + + let inner = Arc::new(AimDbInner { records }); + let runtime = Arc::new(()); + let emitter = Emitter::new(runtime, inner); + + let _emitter2 = emitter.clone(); + } +} diff --git a/aimdb-core/src/error.rs b/aimdb-core/src/error.rs index 05e6ddba..22339eb9 100644 --- a/aimdb-core/src/error.rs +++ b/aimdb-core/src/error.rs @@ -429,6 +429,15 @@ pub enum DbError { _message: (), }, + /// Runtime execution errors (task spawning, scheduling, etc.) + #[cfg_attr(feature = "std", error("Runtime error: {message}"))] + RuntimeError { + #[cfg(feature = "std")] + message: String, + #[cfg(not(feature = "std"))] + _message: (), + }, + /// I/O operation errors (std only) #[cfg(feature = "std")] #[error("I/O error: {source}")] @@ -480,6 +489,7 @@ impl core::fmt::Display for DbError { DbError::ResourceUnavailable { .. } => (0x5002, "Resource unavailable"), DbError::HardwareError { .. } => (0x6001, "Hardware error"), DbError::Internal { .. } => (0x7001, "Internal error"), + DbError::RuntimeError { .. } => (0x7002, "Runtime error"), // Standard library only errors (conditionally compiled) #[cfg(feature = "std")] @@ -618,6 +628,9 @@ impl DbError { // Internal errors: 0x7000-0x7FFF DbError::Internal { .. } => 0x7001, + // Runtime errors: 0xA000-0xAFFF + DbError::RuntimeError { .. } => 0xA001, + // Standard library only errors (conditionally compiled) // I/O errors: 0x8000-0x8FFF #[cfg(feature = "std")] @@ -771,6 +784,11 @@ impl DbError { DbError::Internal { code, message } } + DbError::RuntimeError { mut message } => { + Self::prepend_context_always(&mut message, context); + DbError::RuntimeError { message } + } + // For Io and Json errors, convert to context variants #[cfg(feature = "std")] DbError::Io { source } => DbError::IoWithContext { diff --git a/aimdb-core/src/lib.rs b/aimdb-core/src/lib.rs index c7a807fe..11da3df7 100644 --- a/aimdb-core/src/lib.rs +++ b/aimdb-core/src/lib.rs @@ -1,12 +1,46 @@ //! AimDB Core Database Engine //! -//! This crate provides the core database engine for AimDB, supporting async -//! in-memory storage with real-time synchronization across MCU → edge → cloud -//! environments. +//! Type-safe, async in-memory database for real-time data synchronization +//! across MCU → edge → cloud environments. +//! +//! # Architecture +//! +//! - **Type-Safe Records**: `TypeId`-based routing, no string keys +//! - **Unified API**: Single `Database` type for all operations +//! - **Runtime Agnostic**: Works with Tokio (std) or Embassy (embedded) +//! - **Producer-Consumer**: Built-in typed message passing +//! +//! See examples in the repository for usage patterns. #![cfg_attr(not(feature = "std"), no_std)] +pub mod builder; +pub mod context; +pub mod database; +pub mod emitter; mod error; +pub mod metrics; +pub mod producer_consumer; +pub mod runtime; +pub mod time; +pub mod tracked_fn; +pub mod typed_record; // Public API exports +pub use context::RuntimeContext; pub use error::{DbError, DbResult}; +pub use runtime::{ + ExecutorError, ExecutorResult, Logger, Runtime, RuntimeAdapter, RuntimeInfo, Sleeper, Spawn, + TimeOps, TimeSource, +}; + +// Database implementation exports +pub use database::{Database, DatabaseSpec, DatabaseSpecBuilder}; + +// Producer-Consumer Pattern exports +pub use builder::{AimDb, AimDbBuilder}; +pub use emitter::Emitter; +pub use metrics::CallStats; +pub use producer_consumer::{RecordRegistrar, RecordT}; +pub use tracked_fn::TrackedAsyncFn; +pub use typed_record::{AnyRecord, AnyRecordExt, TypedRecord}; diff --git a/aimdb-core/src/metrics.rs b/aimdb-core/src/metrics.rs new file mode 100644 index 00000000..aa497557 --- /dev/null +++ b/aimdb-core/src/metrics.rs @@ -0,0 +1,245 @@ +//! Observability and metrics for producer-consumer patterns +//! +//! Provides lock-free call tracking for producers and consumers, +//! enabling runtime inspection and debugging. + +use core::fmt::Debug; +use core::sync::atomic::{AtomicU32, Ordering}; + +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; +#[cfg(feature = "std")] +use std::sync::Arc; + +#[cfg(not(feature = "std"))] +extern crate alloc; + +// For mutex, we use different strategies based on std/no_std +#[cfg(feature = "std")] +use std::sync::Mutex; + +#[cfg(not(feature = "std"))] +use spin::Mutex; + +/// Statistics tracker for producer/consumer calls +/// +/// Tracks the number of invocations and the last argument value +/// in a lock-free manner using atomic operations where possible. +/// +/// # Design +/// +/// - Call counting uses `AtomicU32` for embedded compatibility +/// - Last argument storage uses minimal locking (spin::Mutex in no_std) +/// - Generic over argument type for type safety +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::experimental::CallStats; +/// +/// let stats = CallStats::::new(); +/// stats.record(&42); +/// stats.record(&100); +/// +/// assert_eq!(stats.calls(), 2); +/// assert_eq!(stats.last_arg(), Some(100)); +/// ``` +#[derive(Debug)] +pub struct CallStats { + /// Atomic counter for number of calls (u32 for embedded compatibility) + calls: AtomicU32, + + /// Last argument value (requires minimal locking) + last_arg: Mutex>, +} + +impl CallStats { + /// Creates a new call statistics tracker + /// + /// # Returns + /// A new `CallStats` with zero calls and no recorded argument + pub fn new() -> Self { + Self { + calls: AtomicU32::new(0), + last_arg: Mutex::new(None), + } + } + + /// Records a function call with the given argument + /// + /// Updates both the call counter and stores the argument value. + /// + /// # Arguments + /// * `arg` - The argument to record + /// + /// # Example + /// + /// ```rust,ignore + /// stats.record(&SensorData { temp: 23.5 }); + /// ``` + pub fn record(&self, arg: &T) { + // Increment call counter atomically + self.calls.fetch_add(1, Ordering::Relaxed); + + // Store last argument (requires brief lock) + #[cfg(feature = "std")] + { + *self.last_arg.lock().unwrap() = Some(arg.clone()); + } + + #[cfg(not(feature = "std"))] + { + *self.last_arg.lock() = Some(arg.clone()); + } + } + + /// Returns the total number of calls recorded + /// + /// # Returns + /// The call count as `u64` + /// + /// # Example + /// + /// ```rust,ignore + /// let count = stats.calls(); + /// println!("Function called {} times", count); + /// ``` + pub fn calls(&self) -> u64 { + self.calls.load(Ordering::Relaxed) as u64 + } + + /// Returns the last recorded argument value + /// + /// # Returns + /// `Some(T)` if at least one call has been recorded, `None` otherwise + /// + /// # Example + /// + /// ```rust,ignore + /// if let Some(last) = stats.last_arg() { + /// println!("Last value: {:?}", last); + /// } + /// ``` + pub fn last_arg(&self) -> Option { + #[cfg(feature = "std")] + { + self.last_arg.lock().unwrap().clone() + } + + #[cfg(not(feature = "std"))] + { + self.last_arg.lock().clone() + } + } + + /// Resets the statistics to initial state + /// + /// Useful for testing or when reusing statistics trackers. + /// + /// # Example + /// + /// ```rust,ignore + /// stats.reset(); + /// assert_eq!(stats.calls(), 0); + /// assert_eq!(stats.last_arg(), None); + /// ``` + pub fn reset(&self) { + self.calls.store(0, Ordering::Relaxed); + + #[cfg(feature = "std")] + { + *self.last_arg.lock().unwrap() = None; + } + + #[cfg(not(feature = "std"))] + { + *self.last_arg.lock() = None; + } + } +} + +impl Default for CallStats { + fn default() -> Self { + Self::new() + } +} + +// Wrap in Arc for easy sharing +impl CallStats { + /// Creates a new `Arc>` for efficient sharing + /// + /// # Returns + /// An `Arc` wrapping a new `CallStats` + /// + /// # Example + /// + /// ```rust,ignore + /// let stats = CallStats::::shared(); + /// // Can be cloned and shared across tasks + /// let stats2 = stats.clone(); + /// ``` + pub fn shared() -> Arc { + Arc::new(Self::new()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_call_stats_basic() { + let stats = CallStats::::new(); + + assert_eq!(stats.calls(), 0); + assert_eq!(stats.last_arg(), None); + + stats.record(&42); + assert_eq!(stats.calls(), 1); + assert_eq!(stats.last_arg(), Some(42)); + + stats.record(&100); + assert_eq!(stats.calls(), 2); + assert_eq!(stats.last_arg(), Some(100)); + } + + #[test] + fn test_call_stats_reset() { + let stats = CallStats::::new(); + + stats.record(&42); + stats.record(&100); + assert_eq!(stats.calls(), 2); + + stats.reset(); + assert_eq!(stats.calls(), 0); + assert_eq!(stats.last_arg(), None); + } + + #[test] + fn test_call_stats_shared() { + let stats = CallStats::::shared(); + let stats2 = stats.clone(); + + stats.record(&42); + assert_eq!(stats2.calls(), 1); + assert_eq!(stats2.last_arg(), Some(42)); + } + + #[derive(Debug, Clone)] + struct TestData { + value: i32, + } + + #[test] + fn test_call_stats_custom_type() { + let stats = CallStats::::new(); + + let data = TestData { value: 42 }; + stats.record(&data); + + assert_eq!(stats.calls(), 1); + let last = stats.last_arg().unwrap(); + assert_eq!(last.value, 42); + } +} diff --git a/aimdb-core/src/producer_consumer.rs b/aimdb-core/src/producer_consumer.rs new file mode 100644 index 00000000..52912c66 --- /dev/null +++ b/aimdb-core/src/producer_consumer.rs @@ -0,0 +1,257 @@ +//! Self-registering records with producer-consumer pattern +//! +//! Provides the `RecordT` trait for self-registering records and +//! the `RecordRegistrar` for fluent registration API. + +use core::fmt::Debug; +use core::future::Future; + +use crate::typed_record::TypedRecord; + +/// Registrar for configuring a typed record +/// +/// Provides a fluent API for registering producer and consumer functions +/// for a specific record type. +/// +/// # Type Parameters +/// * `T` - The record type +/// +/// # Example +/// +/// ```rust,ignore +/// reg.producer(|emitter, data| async move { +/// println!("Producer: {:?}", data); +/// }) +/// .consumer(|emitter, data| async move { +/// println!("Consumer 1: {:?}", data); +/// }) +/// .consumer(|emitter, data| async move { +/// println!("Consumer 2: {:?}", data); +/// }); +/// ``` +pub struct RecordRegistrar<'a, T: Send + 'static + Debug + Clone> { + /// The typed record being configured + pub(crate) rec: &'a mut TypedRecord, +} + +impl<'a, T> RecordRegistrar<'a, T> +where + T: Send + 'static + Debug + Clone, +{ + /// Registers a producer function + /// + /// The producer is called first when data is produced for this record type. + /// Only one producer can be registered per record type. + /// + /// # Arguments + /// * `f` - An async function taking `(Emitter, T)` and returning `()` + /// + /// # Returns + /// `&mut Self` for method chaining + /// + /// # Panics + /// Panics if a producer is already registered + /// + /// # Example + /// + /// ```rust,ignore + /// reg.producer(|emitter, data| async move { + /// println!("[producer] Processing: {:?}", data); + /// + /// // Can emit to other record types + /// if data.should_alert() { + /// emitter.emit(Alert::from(data)).await?; + /// } + /// }); + /// ``` + pub fn producer(&'a mut self, f: F) -> &'a mut Self + where + F: Fn(crate::emitter::Emitter, T) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + self.rec.set_producer(f); + self + } + + /// Registers a consumer function + /// + /// Consumers are called after the producer, in the order they are registered. + /// Multiple consumers can be registered for the same record type. + /// + /// # Arguments + /// * `f` - An async function taking `(Emitter, T)` and returning `()` + /// + /// # Returns + /// `&mut Self` for method chaining + /// + /// # Example + /// + /// ```rust,ignore + /// reg.consumer(|emitter, data| async move { + /// println!("[consumer] Received: {:?}", data); + /// + /// // Can perform side effects + /// log_to_database(&data).await?; + /// }) + /// .consumer(|emitter, data| async move { + /// println!("[consumer 2] Also received: {:?}", data); + /// + /// // Multiple consumers get the same data + /// send_to_metrics(&data).await?; + /// }); + /// ``` + pub fn consumer(&'a mut self, f: F) -> &'a mut Self + where + F: Fn(crate::emitter::Emitter, T) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + self.rec.add_consumer(f); + self + } +} + +/// Self-registering record trait +/// +/// Records implementing this trait can register their own producer and +/// consumer functions, encapsulating their behavior with their type. +/// +/// # Type Parameters +/// * `Config` - The configuration type for this record +/// +/// # Design Philosophy +/// +/// - **Self-Contained**: Records define their own behavior +/// - **Type-Safe**: Configuration is strongly typed per record +/// - **Composable**: Records can emit to other records via `Emitter` +/// - **Testable**: Configuration can be mocked for testing +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::experimental::{RecordT, RecordRegistrar, Emitter}; +/// +/// #[derive(Clone, Debug)] +/// struct SensorData { +/// temp: f32, +/// } +/// +/// struct SensorConfig { +/// alert_threshold: f32, +/// } +/// +/// impl RecordT for SensorData { +/// type Config = SensorConfig; +/// +/// fn register(reg: &mut RecordRegistrar, cfg: &Self::Config) { +/// let threshold = cfg.alert_threshold; +/// +/// reg.producer(|emitter, data| async move { +/// println!("Sensor reading: {}", data.temp); +/// }) +/// .consumer(move |emitter, data| async move { +/// if data.temp > threshold { +/// let alert = Alert { message: "High temperature!".into() }; +/// let _ = emitter.emit(alert).await; +/// } +/// }); +/// } +/// } +/// ``` +pub trait RecordT: Send + 'static + Debug + Clone { + /// Configuration type for this record + /// + /// This type is passed to the `register` method and can contain + /// any configuration needed to set up producers and consumers. + type Config; + + /// Registers producer and consumer functions + /// + /// This method is called during database construction to set up + /// the data flow for this record type. + /// + /// # Arguments + /// * `reg` - The registrar for adding producer/consumer functions + /// * `cfg` - Configuration specific to this record type + /// + /// # Example + /// + /// ```rust,ignore + /// fn register(reg: &mut RecordRegistrar, cfg: &Self::Config) { + /// // Set up producer + /// reg.producer(|emitter, data| async move { + /// // Process incoming data + /// process(&data).await; + /// }); + /// + /// // Set up consumers + /// reg.consumer(|emitter, data| async move { + /// // React to data + /// react(&data).await; + /// }); + /// } + /// ``` + fn register<'a>(reg: &'a mut RecordRegistrar<'a, Self>, cfg: &Self::Config); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::typed_record::TypedRecord; + + #[derive(Debug, Clone, PartialEq)] + struct TestData { + value: i32, + } + + struct TestConfig { + threshold: i32, + } + + impl RecordT for TestData { + type Config = TestConfig; + + fn register<'a>(reg: &'a mut RecordRegistrar<'a, Self>, cfg: &Self::Config) { + let threshold = cfg.threshold; + + reg.producer(|_emitter, data| async move { + assert!(data.value >= 0); + }) + .consumer(move |_emitter, data| async move { + if data.value > threshold { + // Would emit alert in real usage + } + }); + } + } + + #[test] + fn test_record_registrar() { + let mut record = TypedRecord::::new(); + let cfg = TestConfig { threshold: 50 }; + + { + let mut reg = RecordRegistrar { rec: &mut record }; + TestData::register(&mut reg, &cfg); + } + + // Verify producer and consumer were added + assert!(record.producer_stats().is_some()); + assert_eq!(record.consumer_stats().len(), 1); + } + + #[test] + fn test_record_validation() { + use crate::typed_record::AnyRecord; + + let mut record = TypedRecord::::new(); + let cfg = TestConfig { threshold: 50 }; + + { + let mut reg = RecordRegistrar { rec: &mut record }; + TestData::register(&mut reg, &cfg); + } + + // Should be valid after registration + assert!(record.validate().is_ok()); + } +} diff --git a/aimdb-core/src/runtime.rs b/aimdb-core/src/runtime.rs new file mode 100644 index 00000000..edc59cfc --- /dev/null +++ b/aimdb-core/src/runtime.rs @@ -0,0 +1,54 @@ +//! Runtime adapter traits and implementations for AimDB +//! +//! This module re-exports the runtime traits from aimdb-executor and provides +//! additional runtime-related functionality for the core database. + +// Re-export simplified executor traits +pub use aimdb_executor::{ + ExecutorError, ExecutorResult, Logger, Runtime, RuntimeAdapter, RuntimeInfo, Sleeper, Spawn, + TimeOps, TimeSource, +}; + +/// Convert executor errors to database errors +/// +/// This allows adapters to return `ExecutorError` while the core database +/// works with `DbError` for consistency with the rest of the API. +impl From for crate::DbError { + fn from(err: ExecutorError) -> Self { + match err { + ExecutorError::SpawnFailed { message } => { + #[cfg(feature = "std")] + { + crate::DbError::RuntimeError { message } + } + #[cfg(not(feature = "std"))] + { + let _ = message; // Use the message variable to avoid unused warnings + crate::DbError::RuntimeError { _message: () } + } + } + ExecutorError::RuntimeUnavailable { message } => { + #[cfg(feature = "std")] + { + crate::DbError::RuntimeError { message } + } + #[cfg(not(feature = "std"))] + { + let _ = message; // Use the message variable to avoid unused warnings + crate::DbError::RuntimeError { _message: () } + } + } + ExecutorError::TaskJoinFailed { message } => { + #[cfg(feature = "std")] + { + crate::DbError::RuntimeError { message } + } + #[cfg(not(feature = "std"))] + { + let _ = message; // Use the message variable to avoid unused warnings + crate::DbError::RuntimeError { _message: () } + } + } + } + } +} diff --git a/aimdb-core/src/time.rs b/aimdb-core/src/time.rs new file mode 100644 index 00000000..8b289ae9 --- /dev/null +++ b/aimdb-core/src/time.rs @@ -0,0 +1,235 @@ +//! Time and timing utilities for AimDB +//! +//! This module provides time-related traits and utilities for runtime adapters, +//! including timestamping, delays, and scheduling operations. + +use core::future::Future; + +/// Trait for adapters that provide current time information +/// +/// This trait enables adapters to provide timing information which can be +/// useful for timestamping, performance measurement, and scheduling. +/// +/// # Availability +/// - **Tokio environments**: Uses `std::time::Instant` +/// - **Embassy environments**: Uses `embassy_time::Instant` (when embassy-time feature enabled) +/// - **Basic environments**: May not be available depending on platform +pub trait TimestampProvider { + /// Type representing an instant in time for this runtime + type Instant; + + /// Gets the current timestamp according to the runtime's time source + /// + /// # Returns + /// Current timestamp as the runtime's Instant type + /// + /// # Example + /// ```rust,no_run + /// use aimdb_core::time::TimestampProvider; + /// + /// fn log_operation(provider: &T) { + /// let _timestamp = provider.now(); + /// // Use timestamp for logging, performance measurement, etc. + /// println!("Operation completed"); + /// } + /// ``` + fn now(&self) -> Self::Instant; +} + +/// Trait for adapters that support sleep/delay operations +/// +/// This trait provides the capability to pause execution for a specified duration, +/// useful for implementing delays, rate limiting, and timing-based control flow. +/// +/// # Availability +/// - **Tokio environments**: Full support with `tokio::time::sleep()` +/// - **Embassy environments**: Available when `embassy-time` feature is enabled +/// - **Basic environments**: Not available +pub trait SleepCapable { + /// Type representing a duration for this runtime + type Duration; + + /// Pauses execution for the specified duration + /// + /// This method provides a sleep/delay functionality that pauses the current + /// execution context without blocking other tasks. + /// + /// # Arguments + /// * `duration` - How long to pause execution + /// + /// # Returns + /// A future that completes after the specified duration + /// + /// # Example + /// ```rust,no_run + /// use aimdb_core::time::SleepCapable; + /// use std::time::Duration; + /// + /// async fn rate_limited_operation>( + /// sleeper: &S + /// ) { + /// println!("Starting operation..."); + /// sleeper.sleep(Duration::from_secs(1)).await; + /// println!("Operation completed after delay"); + /// } + /// ``` + fn sleep(&self, duration: Self::Duration) -> impl Future + Send; +} + +/// Utility functions for time-based operations +/// +/// This module provides convenience functions that work with the timing traits +/// to provide common time-based functionality across different platforms. +pub mod utils { + use super::*; + + /// Measures the execution time of an async operation using a timestamp provider + /// + /// This function works in both `std` and `no_std` environments by using the + /// provided `TimestampProvider` for timing measurements. The duration calculation + /// depends on the timestamp provider's `Instant` type supporting subtraction. + pub async fn measure_async(provider: &P, operation: F) -> (T, P::Instant, P::Instant) + where + F: Future, + P: TimestampProvider, + { + let start = provider.now(); + let result = operation.await; + let end = provider.now(); + (result, start, end) + } + + /// Creates a generic timeout error for operations that exceed their time limit + /// + /// This utility function provides a standard way to create timeout errors + /// without depending on specific runtime implementations. + pub fn create_timeout_error(_operation_name: &str) -> crate::DbError { + crate::DbError::ConnectionFailed { + #[cfg(feature = "std")] + endpoint: "timeout".to_string(), + #[cfg(feature = "std")] + reason: format!("{} operation timed out", _operation_name), + #[cfg(not(feature = "std"))] + _endpoint: (), + #[cfg(not(feature = "std"))] + _reason: (), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "std")] + mod std_tests { + use super::*; + use std::time::Instant; + + // Mock providers for testing + struct MockTimestampProvider; + + impl TimestampProvider for MockTimestampProvider { + type Instant = Instant; + + fn now(&self) -> Self::Instant { + Instant::now() + } + } + + #[test] + fn test_timestamp_provider_trait() { + let provider = MockTimestampProvider; + let timestamp1 = provider.now(); + + // Timestamps should be valid Instant values + let timestamp2 = provider.now(); + + // Second timestamp should be greater than or equal to first + assert!(timestamp2 >= timestamp1); + } + + #[test] + fn test_timeout_error_creation() { + let error = utils::create_timeout_error("database query"); + + // Test error format for std feature + #[cfg(feature = "std")] + { + let error_string = format!("{}", error); + assert!(error_string.contains("database query operation timed out")); + } + } + + #[test] + fn test_measure_async_trait_based() { + let provider = MockTimestampProvider; + + // Test that we can measure with the trait-based approach + let runtime = tokio::runtime::Runtime::new().unwrap(); + let (result, start, end) = runtime.block_on(utils::measure_async(&provider, async { + // Simulate work with a small computation + let mut sum = 0; + for i in 0..1000 { + sum += i; + } + sum + })); + + assert_eq!(result, 499500); // Sum of 0..1000 + // End time should be greater than or equal to start time + assert!(end >= start); + } + } + + #[cfg(not(feature = "std"))] + mod no_std_tests { + use super::{utils, TimestampProvider}; + + // Mock timestamp provider for no_std testing + struct MockNoStdTimestampProvider { + counter: core::sync::atomic::AtomicU64, + } + + impl MockNoStdTimestampProvider { + fn new() -> Self { + Self { + counter: core::sync::atomic::AtomicU64::new(0), + } + } + } + + impl TimestampProvider for MockNoStdTimestampProvider { + type Instant = u64; + + fn now(&self) -> Self::Instant { + self.counter + .fetch_add(1, core::sync::atomic::Ordering::Relaxed) + } + } + + #[test] + fn test_timeout_error_creation_no_std() { + let _error = utils::create_timeout_error("operation"); + // In no_std mode, we just verify the error can be created + // without panicking + } + + #[test] + fn test_measure_async_no_std() { + // Test that measure_async works in no_std with a simple timestamp provider + let provider = MockNoStdTimestampProvider::new(); + + // Since we can't use tokio in no_std, we'll test the function signature + // and basic functionality without actually running async code + let future = async { + // Simulate some work + 42 + }; + + // This mainly tests that the function compiles and accepts the right types + // We explicitly drop the future since we can't await it in no_std tests + core::mem::drop(utils::measure_async(&provider, future)); + } + } +} diff --git a/aimdb-core/src/tracked_fn.rs b/aimdb-core/src/tracked_fn.rs new file mode 100644 index 00000000..66c581e0 --- /dev/null +++ b/aimdb-core/src/tracked_fn.rs @@ -0,0 +1,214 @@ +//! Async function wrapper with automatic call tracking +//! +//! Provides type-erased async functions that automatically record +//! execution statistics via `CallStats`. + +use core::fmt::Debug; +use core::future::Future; +use core::pin::Pin; + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, sync::Arc}; + +#[cfg(feature = "std")] +use std::{boxed::Box, sync::Arc}; + +use crate::emitter::Emitter; +use crate::metrics::CallStats; + +/// Type alias for boxed future returning unit +type BoxFutureUnit = Pin + Send + 'static>>; + +/// Type-erased async function +/// +/// Stores a function that takes `(Emitter, T)` and returns a future. +/// Wrapped in Arc for sharing across the closure. +type ErasedAsyncFn = Arc BoxFutureUnit + Send + Sync + 'static>; + +/// Async function wrapper with automatic call tracking +/// +/// Wraps an async function and automatically records call statistics +/// including invocation count and last argument value. +/// +/// # Type Parameters +/// * `T` - The argument type (must be `Send + 'static + Debug + Clone`) +/// +/// # Design +/// +/// - Type-erases async functions for storage in collections +/// - Automatically records each call in `CallStats` +/// - Zero-cost abstraction when not inspecting stats +/// +/// # Example +/// +/// ```rust,ignore +/// use aimdb_core::experimental::TrackedAsyncFn; +/// +/// let func = TrackedAsyncFn::new(|emitter, value: i32| async move { +/// println!("Called with: {}", value); +/// }); +/// +/// func.call(emitter, 42).await; +/// assert_eq!(func.stats().calls(), 1); +/// ``` +pub struct TrackedAsyncFn { + /// The type-erased function (wrapped in Arc for cloning into closure) + func: ErasedAsyncFn, + + /// Statistics tracker + stats: Arc>, +} + +impl TrackedAsyncFn { + /// Creates a new tracked async function + /// + /// Wraps the provided async function and sets up call tracking. + /// + /// # Arguments + /// * `f` - An async function taking `(Emitter, T)` and returning `()` + /// + /// # Returns + /// A `TrackedAsyncFn` that automatically tracks calls + /// + /// # Example + /// + /// ```rust,ignore + /// let func = TrackedAsyncFn::new(|emitter, data| async move { + /// println!("Processing: {:?}", data); + /// // Can emit to other record types + /// emitter.emit(OtherData(data.value)).await?; + /// }); + /// ``` + pub fn new(f: F) -> Self + where + F: Fn(Emitter, T) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + let stats = Arc::new(CallStats::::new()); + let stats_clone = stats.clone(); + + // Wrap the function in Arc so we can move it into the closure + let f = Arc::new(f); + + // Wrap the function to record calls + let boxed: ErasedAsyncFn = Arc::new(move |em, arg| { + let stats_inner = stats_clone.clone(); + let f = f.clone(); + Box::pin(async move { + // Record the call + stats_inner.record(&arg); + + // Execute the actual function + f(em, arg).await + }) + }); + + Self { func: boxed, stats } + } + + /// Calls the wrapped async function + /// + /// This will automatically record the call in statistics before + /// executing the function. + /// + /// # Arguments + /// * `emitter` - The emitter context + /// * `arg` - The argument value + /// + /// # Example + /// + /// ```rust,ignore + /// func.call(emitter.clone(), SensorData { temp: 23.5 }).await; + /// ``` + pub async fn call(&self, emitter: Emitter, arg: T) { + (self.func)(emitter, arg).await + } + + /// Returns the call statistics + /// + /// # Returns + /// An `Arc>` for inspecting call history + /// + /// # Example + /// + /// ```rust,ignore + /// let stats = func.stats(); + /// println!("Called {} times", stats.calls()); + /// println!("Last value: {:?}", stats.last_arg()); + /// ``` + pub fn stats(&self) -> Arc> { + self.stats.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(not(feature = "std"))] + use alloc::collections::BTreeMap; + #[cfg(feature = "std")] + use std::collections::HashMap; + + #[allow(dead_code)] + #[derive(Debug, Clone, PartialEq)] + struct TestData { + value: i32, + } + + #[allow(dead_code)] + fn create_test_emitter() -> Emitter { + #[cfg(not(feature = "std"))] + use alloc::sync::Arc; + #[cfg(feature = "std")] + use std::sync::Arc; + + use crate::builder::AimDbInner; + + #[cfg(feature = "std")] + let records = HashMap::new(); + #[cfg(not(feature = "std"))] + let records = BTreeMap::new(); + + let inner = Arc::new(AimDbInner { records }); + let runtime = Arc::new(()); + + Emitter::new(runtime, inner) + } + + #[tokio::test] + #[cfg(all(test, feature = "tokio-runtime"))] + async fn test_tracked_fn_basic() { + let func = TrackedAsyncFn::new(|_em, data: TestData| async move { + assert_eq!(data.value, 42); + }); + + assert_eq!(func.stats().calls(), 0); + + let emitter = create_test_emitter(); + func.call(emitter, TestData { value: 42 }).await; + + assert_eq!(func.stats().calls(), 1); + assert_eq!(func.stats().last_arg().unwrap().value, 42); + } + + #[tokio::test] + #[cfg(all(test, feature = "tokio-runtime"))] + async fn test_tracked_fn_multiple_calls() { + let func = TrackedAsyncFn::new(|_em, _data: TestData| async move { + // Do nothing + }); + + let emitter = create_test_emitter(); + + func.call(emitter.clone(), TestData { value: 1 }).await; + func.call(emitter.clone(), TestData { value: 2 }).await; + func.call(emitter, TestData { value: 3 }).await; + + assert_eq!(func.stats().calls(), 3); + assert_eq!(func.stats().last_arg().unwrap().value, 3); + } +} diff --git a/aimdb-core/src/typed_record.rs b/aimdb-core/src/typed_record.rs new file mode 100644 index 00000000..4a9a002e --- /dev/null +++ b/aimdb-core/src/typed_record.rs @@ -0,0 +1,311 @@ +//! Type-safe record storage using TypeId +//! +//! This module provides a type-safe alternative to string-based record +//! identification, using Rust's `TypeId` for compile-time type safety. + +use core::any::Any; +use core::fmt::Debug; + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::{boxed::Box, sync::Arc, vec::Vec}; + +#[cfg(feature = "std")] +use std::{boxed::Box, sync::Arc, vec::Vec}; + +use crate::metrics::CallStats; +use crate::tracked_fn::TrackedAsyncFn; + +/// Type-erased trait for records +/// +/// Allows storage of heterogeneous record types in a single collection +/// while maintaining type safety through downcast operations. +/// +/// # Design +/// +/// - Type-erased interface for storage in collections +/// - Safe downcasting to concrete types via `Any` +/// - Validation of producer/consumer configuration +/// +/// # Example +/// +/// ```rust,ignore +/// let mut record: Box = Box::new(TypedRecord::::new()); +/// if let Some(typed) = record.as_any().downcast_ref::>() { +/// // Access type-specific methods +/// } +/// ``` +pub trait AnyRecord: Send + Sync { + /// Validates that the record has correct producer/consumer setup + /// + /// # Returns + /// `Ok(())` if valid, `Err` with description if invalid + /// + /// # Rules + /// - Must have exactly one producer + /// - Must have at least one consumer + fn validate(&self) -> Result<(), &'static str>; + + /// Returns self as Any for downcasting + fn as_any(&self) -> &dyn Any; + + /// Returns self as mutable Any for downcasting + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +// Helper extension trait for type-safe downcasting +pub trait AnyRecordExt { + /// Attempts to downcast to a typed record reference + /// + /// # Type Parameters + /// * `T` - The expected record type + /// + /// # Returns + /// `Some(&TypedRecord)` if types match, `None` otherwise + fn as_typed(&self) -> Option<&TypedRecord>; + + /// Attempts to downcast to a mutable typed record reference + /// + /// # Type Parameters + /// * `T` - The expected record type + /// + /// # Returns + /// `Some(&mut TypedRecord)` if types match, `None` otherwise + fn as_typed_mut(&mut self) -> Option<&mut TypedRecord>; +} + +impl AnyRecordExt for Box { + fn as_typed(&self) -> Option<&TypedRecord> { + self.as_any().downcast_ref::>() + } + + fn as_typed_mut(&mut self) -> Option<&mut TypedRecord> { + self.as_any_mut().downcast_mut::>() + } +} + +/// Typed record storage with producer/consumer functions +/// +/// Stores type-safe producer and consumer functions for a specific +/// record type T, along with their execution statistics. +/// +/// # Type Parameters +/// * `T` - The record type (must be `Send + 'static + Debug + Clone`) +/// +/// # Design +/// +/// - One optional producer function +/// - Multiple consumer functions (Vec) +/// - Each function is wrapped in `TrackedAsyncFn` for observability +/// +/// # Example +/// +/// ```rust,ignore +/// let mut record = TypedRecord::::new(); +/// record.set_producer(|em, data| async move { +/// println!("Producer: {:?}", data); +/// }); +/// record.add_consumer(|em, data| async move { +/// println!("Consumer: {:?}", data); +/// }); +/// ``` +pub struct TypedRecord { + /// Optional producer function + producer: Option>, + + /// List of consumer functions + consumers: Vec>, +} + +impl TypedRecord { + /// Creates a new empty typed record + /// + /// # Returns + /// A `TypedRecord` with no producer or consumers + pub fn new() -> Self { + Self { + producer: None, + consumers: Vec::new(), + } + } + + /// Sets the producer function for this record + /// + /// # Arguments + /// * `f` - An async function taking `(Emitter, T)` and returning `()` + /// + /// # Panics + /// Panics if a producer is already set (each record can have only one producer) + /// + /// # Example + /// + /// ```rust,ignore + /// record.set_producer(|emitter, data| async move { + /// println!("Processing: {:?}", data); + /// // Can emit to other record types + /// emitter.emit(OtherType(data.value)).await?; + /// }); + /// ``` + pub fn set_producer(&mut self, f: F) + where + F: Fn(crate::emitter::Emitter, T) -> Fut + Send + Sync + 'static, + Fut: core::future::Future + Send + 'static, + { + if self.producer.is_some() { + panic!("This record type already has a producer"); + } + self.producer = Some(TrackedAsyncFn::new(f)); + } + + /// Adds a consumer function for this record + /// + /// Multiple consumers can be registered for the same record type. + /// They will all be called when data is produced. + /// + /// # Arguments + /// * `f` - An async function taking `(Emitter, T)` and returning `()` + /// + /// # Example + /// + /// ```rust,ignore + /// record.add_consumer(|emitter, data| async move { + /// println!("Consumer 1: {:?}", data); + /// }); + /// record.add_consumer(|emitter, data| async move { + /// println!("Consumer 2: {:?}", data); + /// }); + /// ``` + pub fn add_consumer(&mut self, f: F) + where + F: Fn(crate::emitter::Emitter, T) -> Fut + Send + Sync + 'static, + Fut: core::future::Future + Send + 'static, + { + self.consumers.push(TrackedAsyncFn::new(f)); + } + + /// Produces a value by calling producer and all consumers + /// + /// This is the core of the data flow mechanism: + /// 1. Calls the producer function (if set) + /// 2. Calls all consumer functions in order + /// + /// # Arguments + /// * `emitter` - The emitter context for cross-record communication + /// * `val` - The value to produce + /// + /// # Example + /// + /// ```rust,ignore + /// record.produce(emitter.clone(), SensorData { temp: 23.5 }).await; + /// ``` + pub async fn produce(&self, emitter: crate::emitter::Emitter, val: T) { + // Call producer if present + if let Some(p) = &self.producer { + p.call(emitter.clone(), val.clone()).await; + } + + // Call all consumers + for c in &self.consumers { + c.call(emitter.clone(), val.clone()).await; + } + } + + /// Returns statistics for the producer function + /// + /// # Returns + /// `Some(Arc>)` if producer is set, `None` otherwise + pub fn producer_stats(&self) -> Option>> { + self.producer.as_ref().map(|p| p.stats()) + } + + /// Returns statistics for all consumer functions + /// + /// # Returns + /// A vector of `Arc>`, one for each consumer + pub fn consumer_stats(&self) -> Vec>> { + self.consumers.iter().map(|c| c.stats()).collect() + } +} + +impl Default for TypedRecord { + fn default() -> Self { + Self::new() + } +} + +impl AnyRecord for TypedRecord { + fn validate(&self) -> Result<(), &'static str> { + if self.producer.is_none() { + return Err("must have exactly one producer"); + } + if self.consumers.is_empty() { + return Err("must have ≥1 consumer"); + } + Ok(()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, Clone, PartialEq)] + struct TestData { + value: i32, + } + + #[test] + fn test_typed_record_new() { + let record = TypedRecord::::new(); + assert!(record.producer.is_none()); + assert!(record.consumers.is_empty()); + } + + #[test] + fn test_typed_record_validation() { + let mut record = TypedRecord::::new(); + + // No producer, no consumers - invalid + assert!(record.validate().is_err()); + + // Add producer - still invalid (no consumers) + record.set_producer(|_em, _data| async {}); + assert!(record.validate().is_err()); + + // Add consumer - now valid + record.add_consumer(|_em, _data| async {}); + assert!(record.validate().is_ok()); + } + + #[test] + #[should_panic(expected = "already has a producer")] + fn test_typed_record_duplicate_producer() { + let mut record = TypedRecord::::new(); + record.set_producer(|_em, _data| async {}); + record.set_producer(|_em, _data| async {}); // Should panic + } + + #[test] + fn test_any_record_downcast() { + use super::AnyRecordExt; + + let mut record: Box = Box::new(TypedRecord::::new()); + + // Should successfully downcast to correct type + assert!(record.as_typed::().is_some()); + assert!(record.as_typed_mut::().is_some()); + + // Should fail to downcast to wrong type + assert!(record.as_typed::().is_none()); + } +} diff --git a/aimdb-embassy-adapter/Cargo.toml b/aimdb-embassy-adapter/Cargo.toml index ec1dcb5f..f3c3d928 100644 --- a/aimdb-embassy-adapter/Cargo.toml +++ b/aimdb-embassy-adapter/Cargo.toml @@ -10,14 +10,10 @@ build = "build.rs" default = [] # Runtime features -embassy-runtime = [ - "embassy-executor", - "embassy-time", - "aimdb-core/embassy-runtime", -] +embassy-runtime = ["embassy-executor", "embassy-time", "embassy-sync"] # Observability features (no_std compatible) -tracing = ["aimdb-core/tracing"] +tracing = ["aimdb-core/tracing", "dep:tracing"] # Note: std feature is intentionally kept but will cause build.rs to fail if enabled # This provides clear error messages when accidentally enabled @@ -26,20 +22,41 @@ std = [] # Note: metrics feature not supported on embedded - requires std library [dependencies] -# Core AimDB types - always no_std for Embassy +# Executor traits +aimdb-executor = { path = "../aimdb-executor", default-features = false, features = [ + "embassy-types", +] } + +# Core AimDB types - no_std for Embassy (for DbError integration) aimdb-core = { path = "../aimdb-core", default-features = false, features = [ - "embedded", + "embassy-runtime", ] } # Embassy ecosystem for embedded async embassy-executor = { workspace = true, optional = true } embassy-time = { workspace = true, optional = true } +embassy-sync = { workspace = true, optional = true } # Embedded HAL for peripheral abstractions embedded-hal = { workspace = true } embedded-hal-async = { workspace = true } embedded-hal-nb = { workspace = true } +# Logging for embedded +defmt = { workspace = true } + +# Observability (optional, no_std compatible) +tracing = { workspace = true, optional = true, default-features = false } + [dev-dependencies] -# For testing on embedded targets +# For testing on embedded targets heapless = "0.9.1" + +# Utilities for async testing +futures = "0.3" + +# For tracing tests +tracing-test = "0.2" + +# Random number generation for tests +rand = "0.8" diff --git a/aimdb-embassy-adapter/build.rs b/aimdb-embassy-adapter/build.rs index 017e973c..d2a32adc 100644 --- a/aimdb-embassy-adapter/build.rs +++ b/aimdb-embassy-adapter/build.rs @@ -8,18 +8,18 @@ use std::env; fn main() { println!("cargo:rerun-if-changed=build.rs"); - // Validate feature combinations for embedded target + // Validate feature combinations for no_std target let std_enabled = env::var("CARGO_FEATURE_STD").is_ok(); let _embassy_runtime_enabled = env::var("CARGO_FEATURE_EMBASSY_RUNTIME").is_ok(); let metrics_enabled = env::var("CARGO_FEATURE_METRICS").is_ok(); - // Prevent std usage on embassy adapter + // Prevent std usage on embassy adapter (embassy is no_std only) if std_enabled { panic!( r#" ❌ Invalid feature combination: aimdb-embassy-adapter cannot use 'std' features - Embassy adapter is designed exclusively for no_std embedded environments. + Embassy adapter is designed exclusively for no_std environments. Valid embassy features: • embassy-runtime (core embassy runtime) @@ -30,11 +30,11 @@ fn main() { ); } - // Prevent std-only features on embedded + // Prevent std-only features on no_std if metrics_enabled { panic!( r#" -❌ Invalid feature: 'metrics' not supported on embedded targets +❌ Invalid feature: 'metrics' not supported on no_std targets Metrics collection requires std library support. @@ -45,6 +45,7 @@ fn main() { // Always enforce no_std compilation println!("cargo:rustc-cfg=no_std_enforced"); + println!("cargo:rustc-cfg=feature_no_std"); // Set target-specific configurations for embedded targets let target = env::var("TARGET").unwrap_or_default(); diff --git a/aimdb-embassy-adapter/src/database.rs b/aimdb-embassy-adapter/src/database.rs new file mode 100644 index 00000000..3d16b796 --- /dev/null +++ b/aimdb-embassy-adapter/src/database.rs @@ -0,0 +1,76 @@ +//! Embassy Database Implementation +//! +//! This module provides Embassy-specific extensions to the core database, +//! including the build() method that requires an Embassy Spawner. + +use crate::runtime::EmbassyAdapter; +use aimdb_core::{Database, DatabaseSpec, DatabaseSpecBuilder}; + +#[cfg(feature = "embassy-runtime")] +use embassy_executor::Spawner; + +/// Type alias for Embassy database +/// +/// This provides a convenient type for working with databases on the Embassy runtime. +pub type EmbassyDatabase = Database; + +/// Type alias for Embassy database specification +/// +/// This is a convenience type for database specifications used with Embassy. +pub type EmbassyDatabaseSpec = DatabaseSpec; + +/// Type alias for Embassy-specific database specification builder +pub type EmbassyDatabaseSpecBuilder = DatabaseSpecBuilder; + +/// Extension trait for building Embassy databases +/// +/// This trait adds a build() method to DatabaseSpecBuilder +/// that requires a Spawner, reflecting Embassy's runtime requirements. +#[cfg(feature = "embassy-runtime")] +pub trait EmbassyDatabaseBuilder { + /// Builds an Embassy database from the specification + /// + /// This method creates a new EmbassyAdapter with the provided spawner + /// and initializes the database with the configured records. + /// + /// # Arguments + /// * `spawner` - The Embassy spawner for task management + /// + /// # Returns + /// A configured Embassy database ready for use + /// + /// # Example + /// ```rust,no_run + /// # #[cfg(all(not(feature = "std"), feature = "embassy-runtime"))] + /// # { + /// use embassy_executor::Spawner; + /// use aimdb_core::Database; + /// use aimdb_embassy_adapter::{EmbassyAdapter, EmbassyDatabaseBuilder}; + /// + /// #[embassy_executor::main] + /// async fn main(spawner: Spawner) { + /// let db = Database::::builder() + /// .record("sensors") + /// .record("metrics") + /// .build(spawner); + /// + /// // Use the database + /// } + /// # } + /// ``` + fn build(self, spawner: Spawner) -> Database; +} + +#[cfg(feature = "embassy-runtime")] +impl EmbassyDatabaseBuilder for DatabaseSpecBuilder { + fn build(self, spawner: Spawner) -> Database { + #[cfg(feature = "tracing")] + tracing::info!("Building Embassy database with typed records and spawner"); + + let adapter = EmbassyAdapter::new_with_spawner(spawner); + let spec = self.into_spec(); + // Embassy build doesn't return Result, so we panic on error + // This is consistent with Embassy's error handling philosophy + Database::new(adapter, spec).expect("Failed to build Embassy database") + } +} diff --git a/aimdb-embassy-adapter/src/lib.rs b/aimdb-embassy-adapter/src/lib.rs index a7c57670..8a7f212f 100644 --- a/aimdb-embassy-adapter/src/lib.rs +++ b/aimdb-embassy-adapter/src/lib.rs @@ -56,10 +56,92 @@ #![no_std] +extern crate alloc; + // Only include the implementation when std feature is not enabled // Embassy adapter is designed exclusively for no_std environments #[cfg(not(feature = "std"))] mod error; +#[cfg(not(feature = "std"))] +mod runtime; + +#[cfg(not(feature = "std"))] +pub mod database; + +#[cfg(all(not(feature = "std"), feature = "embassy-time"))] +pub mod time; + +// Error handling exports #[cfg(not(feature = "std"))] pub use error::{EmbassyErrorConverter, EmbassyErrorSupport}; + +// Runtime adapter exports +#[cfg(feature = "embassy-runtime")] +pub use runtime::EmbassyAdapter; + +// Database implementation exports +#[cfg(feature = "embassy-runtime")] +pub use database::{ + EmbassyDatabase, EmbassyDatabaseBuilder, EmbassyDatabaseSpec, EmbassyDatabaseSpecBuilder, +}; + +// Re-export core types for convenience +#[cfg(all(not(feature = "std"), feature = "embassy-runtime"))] +pub use embassy_executor::Spawner; + +/// Embassy-specific delay function +/// +/// Delays execution for the specified number of milliseconds using Embassy's +/// timer system. This should be used in services running on Embassy runtime. +/// +/// # Arguments +/// * `ms` - Number of milliseconds to delay +/// +/// # Example +/// ```rust,no_run +/// # #[cfg(all(not(feature = "std"), feature = "embassy-time"))] +/// # { +/// use aimdb_embassy_adapter::delay_ms; +/// +/// # async fn example() { +/// delay_ms(1000).await; // Wait 1 second using Embassy timer +/// # } +/// # } +/// ``` +#[cfg(all(not(feature = "std"), feature = "embassy-time"))] +pub async fn delay_ms(ms: u64) { + embassy_time::Timer::after(embassy_time::Duration::from_millis(ms)).await; +} + +/// Embassy-specific yield function +/// +/// Yields control to allow other Embassy tasks to run. This provides +/// cooperative scheduling within the Embassy async runtime. +/// +/// # Example +/// ```rust,no_run +/// # #[cfg(not(feature = "std"))] +/// # { +/// use aimdb_embassy_adapter::yield_now; +/// +/// # async fn example() { +/// yield_now().await; +/// # } +/// # } +/// ``` +#[cfg(not(feature = "std"))] +pub async fn yield_now() { + // Embassy doesn't have a built-in yield_now, but we can simulate it + // by using a very short timer delay + #[cfg(feature = "embassy-time")] + embassy_time::Timer::after(embassy_time::Duration::from_millis(0)).await; + + // If embassy-time is not available, we can use core::future::pending + // and immediately wake, but for simplicity, we'll use a no-op for now + #[cfg(not(feature = "embassy-time"))] + { + // Simple yield implementation - just await a ready future + core::future::ready(()).await; + } +} diff --git a/aimdb-embassy-adapter/src/runtime.rs b/aimdb-embassy-adapter/src/runtime.rs new file mode 100644 index 00000000..74193709 --- /dev/null +++ b/aimdb-embassy-adapter/src/runtime.rs @@ -0,0 +1,209 @@ +//! Embassy Runtime Adapter for AimDB +//! +//! This module provides the Embassy-specific implementation of AimDB's runtime traits, +//! enabling async task execution in embedded environments using Embassy. + +use aimdb_core::{DbError, DbResult}; +use aimdb_executor::{ExecutorResult, Logger, RuntimeAdapter, Spawn, TimeOps}; +use core::future::Future; + +#[cfg(feature = "tracing")] +use tracing::{debug, warn}; + +#[cfg(feature = "embassy-runtime")] +use embassy_executor::Spawner; + +/// Embassy runtime adapter for async task execution in embedded systems +/// +/// This adapter provides AimDB's runtime interface for Embassy-based embedded +/// applications. It can either work standalone or store an Embassy spawner +/// for integrated task management. +/// +/// # Example +/// ```rust,no_run +/// # #[cfg(not(feature = "std"))] +/// # { +/// use aimdb_embassy_adapter::EmbassyAdapter; +/// use aimdb_core::RuntimeAdapter; +/// +/// # async fn example() -> aimdb_core::DbResult<()> { +/// let adapter = EmbassyAdapter::new()?; +/// +/// let result = adapter.spawn_task(async { +/// Ok::<_, aimdb_core::DbError>(42) +/// }).await?; +/// # Ok(()) +/// # } +/// # } +/// ``` +#[derive(Clone)] +pub struct EmbassyAdapter { + #[cfg(feature = "embassy-runtime")] + spawner: Option, + #[cfg(not(feature = "embassy-runtime"))] + _phantom: core::marker::PhantomData<()>, +} + +// SAFETY: EmbassyAdapter only contains an Option and Spawner is thread-safe. +// Embassy executor handles spawner synchronization internally. +unsafe impl Send for EmbassyAdapter {} +unsafe impl Sync for EmbassyAdapter {} + +impl core::fmt::Debug for EmbassyAdapter { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut debug_struct = f.debug_struct("EmbassyAdapter"); + + #[cfg(feature = "embassy-runtime")] + debug_struct.field("spawner", &self.spawner.is_some()); + + #[cfg(not(feature = "embassy-runtime"))] + debug_struct.field("_phantom", &"no spawner support"); + + debug_struct.finish() + } +} + +impl EmbassyAdapter { + /// Creates a new EmbassyAdapter without a spawner + /// + /// This creates a stateless adapter suitable for basic task execution. + /// + /// # Returns + /// `Ok(EmbassyAdapter)` - Always succeeds + pub fn new() -> ExecutorResult { + #[cfg(feature = "tracing")] + debug!("Creating EmbassyAdapter (no spawner)"); + + Ok(Self { + #[cfg(feature = "embassy-runtime")] + spawner: None, + #[cfg(not(feature = "embassy-runtime"))] + _phantom: core::marker::PhantomData, + }) + } + + /// Creates a new EmbassyAdapter returning DbResult for backward compatibility + /// + /// This method provides compatibility with existing code that expects DbResult. + pub fn new_db_result() -> DbResult { + Self::new().map_err(DbError::from) + } + + /// Creates a new EmbassyAdapter with an Embassy spawner + /// + /// This creates an adapter that can use the Embassy spawner for + /// advanced task management operations. + /// + /// # Arguments + /// * `spawner` - The Embassy spawner to use for task management + /// + /// # Returns + /// An EmbassyAdapter configured with the provided spawner + #[cfg(feature = "embassy-runtime")] + pub fn new_with_spawner(spawner: Spawner) -> Self { + #[cfg(feature = "tracing")] + debug!("Creating EmbassyAdapter with spawner"); + + Self { + spawner: Some(spawner), + } + } + + /// Gets a reference to the stored spawner, if any + #[cfg(feature = "embassy-runtime")] + pub fn spawner(&self) -> Option<&Spawner> { + self.spawner.as_ref() + } +} + +impl Default for EmbassyAdapter { + fn default() -> Self { + Self::new().expect("EmbassyAdapter::default() should not fail") + } +} + +// Trait implementations for the simplified core adapter interfaces + +impl RuntimeAdapter for EmbassyAdapter { + fn runtime_name() -> &'static str { + "embassy" + } +} + +// Implement Spawn trait for Embassy (static spawning) +#[cfg(feature = "embassy-runtime")] +impl Spawn for EmbassyAdapter { + type SpawnToken = (); // Embassy doesn't return a handle from static spawn + + fn spawn(&self, _future: F) -> ExecutorResult + where + F: Future + Send + 'static, + { + // Embassy spawning is handled via the #[embassy_executor::task] attribute + // and spawner.spawn() at the application level. This method exists for + // trait compatibility but spawning must be done through Embassy tasks. + Err(aimdb_executor::ExecutorError::RuntimeUnavailable { + message: "Embassy requires static task spawning via #[embassy_executor::task]", + }) + } +} + +// Implement TimeOps trait (combines TimeSource + Sleeper) +#[cfg(feature = "embassy-time")] +impl TimeOps for EmbassyAdapter { + type Instant = embassy_time::Instant; + type Duration = embassy_time::Duration; + + fn now(&self) -> Self::Instant { + embassy_time::Instant::now() + } + + fn duration_since( + &self, + later: Self::Instant, + earlier: Self::Instant, + ) -> Option { + if later >= earlier { + Some(later - earlier) + } else { + None + } + } + + fn millis(&self, millis: u64) -> Self::Duration { + embassy_time::Duration::from_millis(millis) + } + + fn secs(&self, secs: u64) -> Self::Duration { + embassy_time::Duration::from_secs(secs) + } + + fn micros(&self, micros: u64) -> Self::Duration { + embassy_time::Duration::from_micros(micros) + } + + fn sleep(&self, duration: Self::Duration) -> impl Future + Send { + embassy_time::Timer::after(duration) + } +} + +// Implement Logger trait +impl Logger for EmbassyAdapter { + fn info(&self, message: &str) { + defmt::info!("{}", message); + } + + fn debug(&self, message: &str) { + defmt::debug!("{}", message); + } + + fn warn(&self, message: &str) { + defmt::warn!("{}", message); + } + + fn error(&self, message: &str) { + defmt::error!("{}", message); + } +} + +// Runtime trait is auto-implemented when RuntimeAdapter + TimeOps + Logger + Spawn are all implemented diff --git a/aimdb-embassy-adapter/src/time.rs b/aimdb-embassy-adapter/src/time.rs new file mode 100644 index 00000000..3fec953e --- /dev/null +++ b/aimdb-embassy-adapter/src/time.rs @@ -0,0 +1,133 @@ +//! Embassy Time Integration for AimDB +//! +//! This module provides Embassy-specific implementations of AimDB's time traits, +//! enabling embedded applications to use timing functionality with Embassy's +//! time driver and timer primitives. + +use aimdb_core::time::{SleepCapable, TimestampProvider}; +use core::future::Future; + +#[cfg(feature = "embassy-time")] +use embassy_time::{Duration, Instant, Timer}; + +#[cfg(feature = "tracing")] +use tracing::{debug, trace}; + +/// Time provider implementing both timestamp and sleep capabilities +/// +/// This provider uses Embassy's time driver to provide both timestamp and sleep +/// functionality in embedded environments. It integrates with Embassy's timer +/// system and works without requiring std. +/// +/// # Example +/// ```rust,no_run +/// # #[cfg(all(not(feature = "std"), feature = "embassy-time"))] +/// # { +/// use aimdb_embassy_adapter::time::TimeProvider; +/// use aimdb_core::time::{TimestampProvider, SleepCapable}; +/// use embassy_time::Duration; +/// +/// # async fn example() { +/// let provider = TimeProvider::new(); +/// +/// // Use as timestamp provider +/// let timestamp = provider.now(); +/// +/// // Use as sleep provider +/// provider.sleep(Duration::from_millis(50)).await; +/// # } +/// # } +/// ``` +#[cfg(feature = "embassy-time")] +#[derive(Debug, Clone, Copy)] +pub struct TimeProvider; + +#[cfg(feature = "embassy-time")] +impl TimeProvider { + /// Creates a new time provider + pub const fn new() -> Self { + Self + } +} + +#[cfg(feature = "embassy-time")] +impl Default for TimeProvider { + fn default() -> Self { + Self::new() + } +} + +#[cfg(feature = "embassy-time")] +impl TimestampProvider for TimeProvider { + type Instant = Instant; + + fn now(&self) -> Self::Instant { + #[cfg(feature = "tracing")] + trace!("Getting timestamp"); + + Instant::now() + } +} + +#[cfg(feature = "embassy-time")] +impl SleepCapable for TimeProvider { + type Duration = Duration; + + fn sleep(&self, duration: Self::Duration) -> impl Future + Send { + #[cfg(feature = "tracing")] + debug!(duration_ms = duration.as_millis(), "Starting sleep"); + + Timer::after(duration) + } +} + +/// Utility functions for Embassy time operations +/// +/// These functions provide convenient ways to perform common time-based +/// operations using Embassy's time primitives. +pub mod utils { + #[cfg(feature = "embassy-time")] + use super::*; + + /// Measures execution time using the time provider + /// + /// This is a convenience function that uses Embassy's timing system + /// to measure how long an async operation takes to complete. + /// + /// # Returns + /// A tuple containing (result, start_instant, end_instant) + /// + /// # Example + /// ```rust,no_run + /// # #[cfg(all(not(feature = "std"), feature = "embassy-time"))] + /// # { + /// use aimdb_embassy_adapter::time::utils; + /// + /// # async fn example() { + /// let (result, start, end) = utils::measure_async(async { + /// // Some async work + /// 42 + /// }).await; + /// + /// // Calculate duration if needed + /// // let duration = end - start; // This depends on Embassy's Instant implementation + /// # } + /// # } + /// ``` + #[cfg(feature = "embassy-time")] + pub async fn measure_async(operation: F) -> (T, Instant, Instant) + where + F: Future, + { + let provider = TimeProvider::new(); + aimdb_core::time::utils::measure_async(&provider, operation).await + } + + /// Creates a timeout error for time operations + /// + /// This provides a timeout error that can be used when operations + /// exceed their time limits in embedded environments. + pub fn create_timeout_error() -> aimdb_core::DbError { + aimdb_core::time::utils::create_timeout_error("Embassy operation") + } +} diff --git a/aimdb-executor/Cargo.toml b/aimdb-executor/Cargo.toml new file mode 100644 index 00000000..3fc101ae --- /dev/null +++ b/aimdb-executor/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "aimdb-executor" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +description = "Pure async executor trait definitions for AimDB - runtime-agnostic abstractions" +keywords.workspace = true +categories.workspace = true + +[features] +default = ["std"] + +# Core capabilities +std = ["thiserror"] + +# Optional runtime-specific types (for concrete return types) +tokio-types = ["tokio", "std"] # Tokio requires std +embassy-types = ["embassy-executor"] # Embassy works in no_std + +[dependencies] +# Error handling (only for std environments) +thiserror = { workspace = true, optional = true } + +# Runtime-specific types (optional, only for concrete trait method signatures) +tokio = { workspace = true, optional = true, default-features = false } +embassy-executor = { workspace = true, optional = true } + +# Core async types (always available) +# Note: We use core::future::Future which is available in no_std + +[dev-dependencies] +# For testing trait implementations +tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/aimdb-executor/src/lib.rs b/aimdb-executor/src/lib.rs new file mode 100644 index 00000000..08a1ac96 --- /dev/null +++ b/aimdb-executor/src/lib.rs @@ -0,0 +1,140 @@ +//! AimDB Executor Traits - Simplified +//! +//! This crate provides pure trait definitions for async execution across different +//! runtime environments. It enables dependency inversion where the core database +//! depends on abstractions rather than concrete runtime implementations. +//! +//! # Design Philosophy (Simplified) +//! +//! - **Runtime Agnostic**: No concrete runtime dependencies, no cfg in traits +//! - **Flat Trait Structure**: 4 simple traits instead of complex hierarchies +//! - **Platform Flexible**: Works across std and no_std environments +//! - **Accessor Pattern**: Enables clean `ctx.log()` and `ctx.time()` usage +//! - **Zero Dependencies**: Pure trait definitions with minimal coupling +//! +//! # Simplified Trait Structure +//! +//! Instead of 12+ traits with complex hierarchies, we now have 4 focused traits: +//! +//! 1. **`RuntimeAdapter`** - Identity and basic metadata +//! 2. **`TimeOps`** - Time operations (enables `ctx.time()` accessor) +//! 3. **`Logger`** - Logging operations (enables `ctx.log()` accessor) +//! 4. **`Spawn`** - Task spawning (adapter-specific implementation) + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::future::Future; + +// ============================================================================ +// Error Types +// ============================================================================ + +pub type ExecutorResult = Result; + +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(thiserror::Error))] +pub enum ExecutorError { + #[cfg_attr(feature = "std", error("Spawn failed: {message}"))] + SpawnFailed { + #[cfg(feature = "std")] + message: String, + #[cfg(not(feature = "std"))] + message: &'static str, + }, + + #[cfg_attr(feature = "std", error("Runtime unavailable: {message}"))] + RuntimeUnavailable { + #[cfg(feature = "std")] + message: String, + #[cfg(not(feature = "std"))] + message: &'static str, + }, + + #[cfg_attr(feature = "std", error("Task join failed: {message}"))] + TaskJoinFailed { + #[cfg(feature = "std")] + message: String, + #[cfg(not(feature = "std"))] + message: &'static str, + }, +} + +// ============================================================================ +// Core Traits (Simplified - 4 traits total) +// ============================================================================ + +/// Core runtime adapter trait - provides identity +pub trait RuntimeAdapter: Send + Sync + 'static { + fn runtime_name() -> &'static str + where + Self: Sized; +} + +/// Time operations trait - enables ctx.time() accessor +pub trait TimeOps: RuntimeAdapter { + type Instant: Clone + Send + Sync + core::fmt::Debug + 'static; + type Duration: Clone + Send + Sync + core::fmt::Debug + 'static; + + fn now(&self) -> Self::Instant; + fn duration_since( + &self, + later: Self::Instant, + earlier: Self::Instant, + ) -> Option; + fn millis(&self, ms: u64) -> Self::Duration; + fn secs(&self, secs: u64) -> Self::Duration; + fn micros(&self, micros: u64) -> Self::Duration; + fn sleep(&self, duration: Self::Duration) -> impl Future + Send; +} + +/// Logging trait - enables ctx.log() accessor +pub trait Logger: RuntimeAdapter { + fn info(&self, message: &str); + fn debug(&self, message: &str); + fn warn(&self, message: &str); + fn error(&self, message: &str); +} + +/// Task spawning trait - adapter-specific implementation +pub trait Spawn: RuntimeAdapter { + type SpawnToken: Send + 'static; + fn spawn(&self, future: F) -> ExecutorResult + where + F: Future + Send + 'static; +} + +// ============================================================================ +// Convenience Trait Bundle +// ============================================================================ + +/// Complete runtime trait bundle +pub trait Runtime: RuntimeAdapter + TimeOps + Logger + Spawn { + fn runtime_info(&self) -> RuntimeInfo + where + Self: Sized, + { + RuntimeInfo { + name: Self::runtime_name(), + } + } +} + +// Auto-implement Runtime for any type with all traits +impl Runtime for T where T: RuntimeAdapter + TimeOps + Logger + Spawn {} + +#[derive(Debug, Clone)] +pub struct RuntimeInfo { + pub name: &'static str, +} + +// ============================================================================ +// Compatibility Aliases (for migration) +// ============================================================================ + +/// OLD: TimeSource - now use TimeOps +pub trait TimeSource: TimeOps {} +impl TimeSource for T {} + +/// OLD: Sleeper - now part of TimeOps +pub trait Sleeper: TimeOps {} +impl Sleeper for T {} diff --git a/aimdb-macros/Cargo.toml b/aimdb-macros/Cargo.toml new file mode 100644 index 00000000..6d78b201 --- /dev/null +++ b/aimdb-macros/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "aimdb-macros" +version = "0.1.0" +edition = "2021" +authors = ["AimDB Contributors"] +description = "Procedural macros for AimDB" +license = "MIT OR Apache-2.0" +repository = "https://github.com/aimdb-dev/aimdb" +keywords = ["database", "async", "embedded", "no-std", "macros"] +categories = ["embedded", "database-implementations", "asynchronous"] + +[lib] +proc-macro = true + +[features] +default = [] +# Enable debug output for macro expansion +debug = [] +# Runtime feature flags (used for conditional compilation in generated code) +tokio-runtime = [] +embassy-runtime = [] + +[dependencies] +# Procedural macro dependencies +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } + +[dev-dependencies] +# For testing macro expansion +trybuild = "1.0" diff --git a/aimdb-macros/src/lib.rs b/aimdb-macros/src/lib.rs new file mode 100644 index 00000000..9ca40ac4 --- /dev/null +++ b/aimdb-macros/src/lib.rs @@ -0,0 +1,5 @@ +//! AimDB Procedural Macros +//! +//! This crate is currently empty as AimDB has been simplified to use +//! direct spawning patterns instead of procedural macros. +//! diff --git a/aimdb-tokio-adapter/Cargo.toml b/aimdb-tokio-adapter/Cargo.toml index f610e168..545d5c1a 100644 --- a/aimdb-tokio-adapter/Cargo.toml +++ b/aimdb-tokio-adapter/Cargo.toml @@ -12,22 +12,32 @@ categories.workspace = true build = "build.rs" [features] -default = ["tokio-runtime"] +default = ["std", "tokio-runtime"] + +# Platform features +std = ["aimdb-core/std"] # Runtime features -tokio-runtime = ["tokio", "aimdb-core/tokio-runtime"] +tokio-runtime = ["tokio", "std"] # Observability features -tracing = ["aimdb-core/tracing"] +tracing = ["aimdb-core/tracing", "dep:tracing"] metrics = ["aimdb-core/metrics", "tokio-runtime"] # Testing features test-utils = ["aimdb-core/test-utils", "tokio-runtime"] [dependencies] -# Core AimDB types - std version for Tokio -aimdb-core = { path = "../aimdb-core", default-features = true, features = [ +# Executor traits +aimdb-executor = { path = "../aimdb-executor", default-features = false, features = [ "std", + "tokio-types", +] } + +# Core AimDB types - std version for Tokio (for DbError integration) +aimdb-core = { path = "../aimdb-core", default-features = false, features = [ + "std", + "tokio-runtime", ] } # Tokio async runtime @@ -36,6 +46,10 @@ tokio = { workspace = true, optional = true, features = [ "rt-multi-thread", ] } +# Observability (optional) +tracing = { workspace = true, optional = true } + [dev-dependencies] # For async testing tokio-test = { workspace = true } +futures = { workspace = true } diff --git a/aimdb-tokio-adapter/src/database.rs b/aimdb-tokio-adapter/src/database.rs new file mode 100644 index 00000000..e970a953 --- /dev/null +++ b/aimdb-tokio-adapter/src/database.rs @@ -0,0 +1,46 @@ +//! Tokio Database Implementation +//! +//! This module provides Tokio-specific extensions to the core database, +//! including the build() method for easy initialization. + +use crate::runtime::TokioAdapter; +use aimdb_core::{Database, DatabaseSpec, DatabaseSpecBuilder, DbResult}; + +/// Type alias for Tokio database +/// +/// This provides a convenient type for working with databases on the Tokio runtime. +pub type TokioDatabase = Database; + +/// Type alias for Tokio database specification +pub type TokioDatabaseSpec = DatabaseSpec; + +/// Type alias for Tokio database specification builder +pub type TokioDatabaseSpecBuilder = DatabaseSpecBuilder; + +/// Extension trait for building Tokio databases +/// +/// This trait adds a build() method to DatabaseSpecBuilder, +/// enabling clean initialization syntax. +pub trait TokioDatabaseBuilder { + /// Builds a Tokio database from the specification + /// + /// This method creates a new TokioAdapter and initializes the database + /// with the configured records. + /// + /// # Returns + /// `DbResult>` - The configured database + /// + /// See the repository examples for complete usage. + fn build(self) -> DbResult>; +} + +impl TokioDatabaseBuilder for DatabaseSpecBuilder { + fn build(self) -> DbResult> { + #[cfg(feature = "tracing")] + tracing::info!("Building Tokio database with typed records"); + + let adapter = TokioAdapter::new()?; + let spec = self.into_spec(); + Database::new(adapter, spec) + } +} diff --git a/aimdb-tokio-adapter/src/lib.rs b/aimdb-tokio-adapter/src/lib.rs index 6902de3c..520371e4 100644 --- a/aimdb-tokio-adapter/src/lib.rs +++ b/aimdb-tokio-adapter/src/lib.rs @@ -6,7 +6,7 @@ //! # Features //! //! - **Tokio Integration**: Seamless integration with Tokio async executor -//! - **Timeout Support**: Comprehensive timeout handling with `tokio::time` +//! - **Time Support**: Timestamp, sleep, and delayed task capabilities with `tokio::time` //! - **Error Handling**: Tokio-specific error conversions and handling //! - **Std Compatible**: Designed for environments with full standard library //! @@ -18,37 +18,29 @@ //! The adapter extends AimDB's core functionality without requiring tokio //! dependencies in the core crate. It provides: //! -//! - Runtime error constructors for Tokio-specific failures -//! - Automatic conversions from tokio error types -//! - Rich error descriptions leveraging std formatting capabilities -//! -//! # Usage -//! -//! ```rust,no_run -//! use aimdb_core::DbError; -//! use aimdb_tokio_adapter::{TokioErrorSupport, TokioErrorConverter}; -//! use std::time::Duration; -//! -//! // Create runtime-specific errors -//! let timeout_error = DbError::from_timeout_error(0x01, Duration::from_millis(5000)); -//! let runtime_error = DbError::from_runtime_error(0x01, "Runtime not available"); -//! let task_error = DbError::from_task_error(0x02, "Task cancelled"); -//! let io_error = DbError::from_io_error(0x01, "Connection refused"); -//! -//! // Use converter functions -//! let timeout = TokioErrorConverter::timeout_error(Duration::from_millis(1000)); -//! let unavailable = TokioErrorConverter::runtime_unavailable(); -//! ``` //! -//! # Error Code Ranges -//! -//! The adapter uses specific error code ranges for different runtime components: +//! - **Runtime Module**: Core async task spawning with `RuntimeAdapter` +//! - **Time Module**: Time-related capabilities like timestamps and sleep +//! - **Error Module**: Runtime error constructors and conversions +//! - Rich error descriptions leveraging std formatting capabilities //! -//! - **Runtime**: 0x7100-0x71FF -//! - **Timeout**: 0x7200-0x72FF -//! - **Task**: 0x7300-0x73FF -//! - **I/O**: 0x7400-0x74FF +//! See the repository examples for complete usage patterns. + +// Tokio adapter always requires std +#[cfg(not(feature = "std"))] +compile_error!("tokio-adapter requires the std feature"); -mod error; +pub mod database; +pub mod error; +pub mod runtime; +pub mod time; pub use error::{TokioErrorConverter, TokioErrorSupport}; + +#[cfg(feature = "tokio-runtime")] +pub use runtime::TokioAdapter; + +#[cfg(feature = "tokio-runtime")] +pub use database::{ + TokioDatabase, TokioDatabaseBuilder, TokioDatabaseSpec, TokioDatabaseSpecBuilder, +}; diff --git a/aimdb-tokio-adapter/src/runtime.rs b/aimdb-tokio-adapter/src/runtime.rs new file mode 100644 index 00000000..462e7a0f --- /dev/null +++ b/aimdb-tokio-adapter/src/runtime.rs @@ -0,0 +1,219 @@ +//! Tokio Runtime Adapter for AimDB +//! +//! This module provides the Tokio-specific implementation of AimDB's runtime traits, +//! enabling async task spawning and execution in std environments using Tokio. + +use aimdb_core::{DbError, DbResult}; +use aimdb_executor::{ExecutorResult, Logger, RuntimeAdapter, Spawn, TimeOps}; +use core::future::Future; +use std::time::{Duration, Instant}; + +#[cfg(feature = "tracing")] +use tracing::{debug, warn}; + +/// Tokio runtime adapter for async task spawning in std environments +/// +/// The TokioAdapter provides AimDB's runtime interface for Tokio-based +/// applications, focusing on task spawning capabilities that leverage Tokio's +/// async executor and scheduling infrastructure. +/// +/// This module provides the core RuntimeAdapter and DelayCapableAdapter implementations. +/// For time-related capabilities (timestamps, sleep), see the `time` module. +/// +/// # Features +/// - Task spawning with Tokio executor integration +/// - Delayed task spawning with `tokio::time::sleep` +/// - Tracing integration for observability (when `tracing` feature enabled) +/// - Zero-cost abstraction over Tokio's async runtime +/// +/// # Example +/// ```rust,no_run +/// use aimdb_tokio_adapter::TokioAdapter; +/// use aimdb_core::RuntimeAdapter; +/// +/// #[tokio::main] +/// async fn main() -> aimdb_core::DbResult<()> { +/// let adapter = TokioAdapter::new()?; +/// +/// let result = adapter.spawn_task(async { +/// Ok::<_, aimdb_core::DbError>(42) +/// }).await?; +/// +/// // Result: 42 +/// Ok(()) +/// } +/// ``` +#[cfg(feature = "tokio-runtime")] +#[derive(Debug, Clone, Copy)] +pub struct TokioAdapter; + +#[cfg(feature = "tokio-runtime")] +impl TokioAdapter { + /// Creates a new TokioAdapter + /// + /// # Returns + /// `Ok(TokioAdapter)` - Tokio adapters are lightweight and cannot fail + pub fn new() -> ExecutorResult { + #[cfg(feature = "tracing")] + debug!("Creating TokioAdapter"); + + Ok(Self) + } + + /// Creates a new TokioAdapter returning DbResult for backward compatibility + /// + /// This method provides compatibility with existing code that expects DbResult. + pub fn new_db_result() -> DbResult { + Self::new().map_err(DbError::from) + } + + /// Spawns an async task on the Tokio executor + /// + /// This method provides the core task spawning functionality for Tokio-based + /// applications. Tasks are spawned on the Tokio executor, which handles + /// scheduling and execution across available threads. + /// + /// # Arguments + /// * `task` - The async task to spawn + /// + /// # Returns + /// `DbResult` where T is the task's success type + /// + /// # Example + /// ```rust,no_run + /// use aimdb_tokio_adapter::TokioAdapter; + /// use aimdb_core::RuntimeAdapter; + /// + /// # #[tokio::main] + /// # async fn main() -> aimdb_core::DbResult<()> { + /// let adapter = TokioAdapter::new()?; + /// + /// let result = adapter.spawn_task(async { + /// // Some async work + /// Ok::(42) + /// }).await?; + /// + /// // Result: 42 + /// # Ok(()) + /// # } + /// ``` + pub async fn spawn_task(&self, task: F) -> DbResult + where + F: Future> + Send + 'static, + T: Send + 'static, + { + #[cfg(feature = "tracing")] + debug!("Spawning async task"); + + let handle = tokio::task::spawn(task); + + match handle.await { + Ok(result) => { + #[cfg(feature = "tracing")] + match &result { + Ok(_) => debug!("Async task completed successfully"), + Err(e) => warn!(?e, "Async task failed"), + } + result + } + Err(join_error) => { + #[cfg(feature = "tracing")] + warn!(?join_error, "Task join failed"); + + use crate::TokioErrorSupport; + Err(DbError::from_join_error(join_error)) + } + } + } +} + +#[cfg(feature = "tokio-runtime")] +impl Default for TokioAdapter { + fn default() -> Self { + Self::new().expect("TokioAdapter::default() should not fail") + } +} + +// Trait implementations for the core adapter interfaces + +#[cfg(feature = "tokio-runtime")] +impl RuntimeAdapter for TokioAdapter { + fn runtime_name() -> &'static str { + "tokio" + } +} + +// Implement Spawn trait for dynamic task spawning +#[cfg(feature = "tokio-runtime")] +impl Spawn for TokioAdapter { + type SpawnToken = tokio::task::JoinHandle<()>; + + fn spawn(&self, future: F) -> ExecutorResult + where + F: Future + Send + 'static, + { + #[cfg(feature = "tracing")] + tracing::debug!("Spawning future on Tokio runtime"); + + Ok(tokio::spawn(future)) + } +} + +// New unified Runtime trait implementations +#[cfg(feature = "tokio-runtime")] +impl TimeOps for TokioAdapter { + type Instant = Instant; + type Duration = Duration; + + fn now(&self) -> Self::Instant { + Instant::now() + } + + fn duration_since( + &self, + later: Self::Instant, + earlier: Self::Instant, + ) -> Option { + later.checked_duration_since(earlier) + } + + fn millis(&self, millis: u64) -> Self::Duration { + Duration::from_millis(millis) + } + + fn secs(&self, secs: u64) -> Self::Duration { + Duration::from_secs(secs) + } + + fn micros(&self, micros: u64) -> Self::Duration { + Duration::from_micros(micros) + } + + fn sleep(&self, duration: Self::Duration) -> impl Future + Send { + tokio::time::sleep(duration) + } +} + +#[cfg(feature = "tokio-runtime")] +impl Logger for TokioAdapter { + fn info(&self, message: &str) { + println!("ℹ️ {}", message); + } + + fn debug(&self, message: &str) { + #[cfg(debug_assertions)] + println!("🔍 {}", message); + #[cfg(not(debug_assertions))] + let _ = message; // Avoid unused variable warning in release + } + + fn warn(&self, message: &str) { + println!("⚠️ {}", message); + } + + fn error(&self, message: &str) { + eprintln!("❌ {}", message); + } +} + +// Runtime trait is auto-implemented when RuntimeAdapter + TimeOps + Logger + Spawn are implemented diff --git a/aimdb-tokio-adapter/src/time.rs b/aimdb-tokio-adapter/src/time.rs new file mode 100644 index 00000000..0d492c94 --- /dev/null +++ b/aimdb-tokio-adapter/src/time.rs @@ -0,0 +1,69 @@ +//! Tokio Time-related Adapter Implementations +//! +//! This module provides Tokio-specific implementations of time-related traits +//! from aimdb-core, including timestamps and sleep capabilities. + +use aimdb_core::time::{SleepCapable, TimestampProvider}; +use core::future::Future; +use std::time::{Duration, Instant}; + +use crate::TokioAdapter; + +/// Implementation of TimestampProvider for TokioAdapter +/// +/// Uses `std::time::Instant` to provide high-resolution timestamps +/// suitable for performance measurement and timing operations. +#[cfg(feature = "tokio-runtime")] +impl TimestampProvider for TokioAdapter { + type Instant = Instant; + + /// Gets the current timestamp using `std::time::Instant::now()` + /// + /// # Returns + /// Current timestamp as `std::time::Instant` + /// + /// # Example + /// ```rust,no_run + /// use aimdb_tokio_adapter::TokioAdapter; + /// use aimdb_core::time::TimestampProvider; + /// + /// let adapter = TokioAdapter::new().unwrap(); + /// let timestamp = adapter.now(); + /// ``` + fn now(&self) -> Self::Instant { + Instant::now() + } +} + +/// Implementation of SleepCapable for TokioAdapter +/// +/// Provides non-blocking sleep capabilities using `tokio::time::sleep`, +/// which pauses execution without blocking the async runtime. +#[cfg(feature = "tokio-runtime")] +impl SleepCapable for TokioAdapter { + type Duration = Duration; + + /// Pauses execution for the specified duration using `tokio::time::sleep` + /// + /// # Arguments + /// * `duration` - How long to pause execution + /// + /// # Returns + /// A future that completes after the specified duration + /// + /// # Example + /// ```rust,no_run + /// use aimdb_tokio_adapter::TokioAdapter; + /// use aimdb_core::time::SleepCapable; + /// use std::time::Duration; + /// + /// # #[tokio::main] + /// # async fn main() { + /// let adapter = TokioAdapter::new().unwrap(); + /// adapter.sleep(Duration::from_millis(100)).await; + /// # } + /// ``` + fn sleep(&self, duration: Self::Duration) -> impl Future + Send { + tokio::time::sleep(duration) + } +} diff --git a/deny.toml b/deny.toml index a88f2437..48a3e025 100644 --- a/deny.toml +++ b/deny.toml @@ -4,9 +4,7 @@ allow = [ "Apache-2.0", "MIT", "BSD-2-Clause", - "BSD-3-Clause", - "ISC", - "Unicode-DFS-2016", + "0BSD", # BSD Zero Clause License - used by embassy-net dependencies "Unicode-3.0", ] diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index ee049390..00000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "aimdb-examples" -version.workspace = true -edition.workspace = true -license.workspace = true -description = "Example applications and demos for AimDB" -publish = false - -[[example]] -name = "quickstart" -path = "quickstart.rs" - -[dependencies] -# Add any dependencies your examples need here diff --git a/examples/embassy-runtime-demo/.cargo/config.toml b/examples/embassy-runtime-demo/.cargo/config.toml new file mode 100644 index 00000000..47814614 --- /dev/null +++ b/examples/embassy-runtime-demo/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv8m.main-none-eabihf] +runner = 'probe-rs run --chip STM32H563ZITx' + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/embassy-runtime-demo/Cargo.toml b/examples/embassy-runtime-demo/Cargo.toml new file mode 100644 index 00000000..c144e6f0 --- /dev/null +++ b/examples/embassy-runtime-demo/Cargo.toml @@ -0,0 +1,88 @@ +[package] +edition = "2024" +name = "embassy-runtime-demo" +version = "0.1.0" +license = "MIT OR Apache-2.0" +publish = false + +[features] +default = ["embassy-runtime"] +embassy-runtime = [] +# Declare but don't enable these features to silence cfg warnings from the service macro +tokio-runtime = [] +std = [] + +[dependencies] +# AimDB dependencies +aimdb-core = { path = "../../aimdb-core", default-features = false, features = [ + "embassy-runtime", +] } +aimdb-embassy-adapter = { path = "../../aimdb-embassy-adapter", default-features = false, features = [ + "embassy-runtime", +] } +aimdb-executor = { path = "../../aimdb-executor", default-features = false, features = [ + "embassy-types", +] } +# aimdb-macros = { path = "../../aimdb-macros" } # No longer used +aimdb-examples-shared = { path = "../shared", default-features = false, features = [ + "embassy-runtime", +] } + +# Embassy ecosystem - override workspace to use different chip +embassy-stm32 = { workspace = true, features = [ + "defmt", + "stm32h563zi", + "memory-x", + "time-driver-any", + "exti", + "unstable-pac", +] } +embassy-sync = { workspace = true, features = ["defmt"] } +embassy-executor = { workspace = true, features = [ + "arch-cortex-m", + "executor-thread", + "defmt", +] } +embassy-time = { workspace = true, features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-net = { workspace = true, features = [ + "defmt", + "tcp", + "dhcpv4", + "medium-ethernet", +] } +embassy-usb = { workspace = true, features = ["defmt"] } +embassy-futures = { workspace = true } + +# Embedded debugging and logging +defmt = { workspace = true } +defmt-rtt = { workspace = true } +panic-probe = { workspace = true } + +# Cortex-M runtime +cortex-m = { workspace = true } +cortex-m-rt = { workspace = true } +critical-section = { workspace = true } +static_cell = { workspace = true } + +# Embedded HAL +embedded-hal = { workspace = true } +embedded-hal-1 = { workspace = true } +embedded-hal-async = { workspace = true } +embedded-io-async = { workspace = true } +embedded-nal-async = { workspace = true } +embedded-storage = { workspace = true } + +# Embedded utilities +heapless = { workspace = true } +micromath = { workspace = true } +stm32-fmc = { workspace = true } +embedded-alloc = "0.6" + +[package.metadata.embassy] +build = [ + { target = "thumbv8m.main-none-eabihf", artifact-dir = "out/examples/stm32h5" }, +] diff --git a/examples/embassy-runtime-demo/build.rs b/examples/embassy-runtime-demo/build.rs new file mode 100644 index 00000000..8cd32d7e --- /dev/null +++ b/examples/embassy-runtime-demo/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/embassy-runtime-demo/flash.sh b/examples/embassy-runtime-demo/flash.sh new file mode 100755 index 00000000..2b08c48a --- /dev/null +++ b/examples/embassy-runtime-demo/flash.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Flash script for embassy-runtime-demo +# +# This script should be run on the HOST machine where probe-rs and hardware are accessible. +# The binary must be built first in the dev container using: cargo build + +set -e + +BINARY="../../target/thumbv8m.main-none-eabihf/debug/embassy-runtime-demo" + +if [ ! -f "$BINARY" ]; then + echo "Error: Binary not found at $BINARY" + echo "Please build it first in the dev container:" + echo " cd examples/embassy-runtime-demo && cargo build" + exit 1 +fi + +echo "Flashing embassy-runtime-demo to STM32H563ZITx..." +probe-rs run --chip STM32H563ZITx "$BINARY" diff --git a/examples/embassy-runtime-demo/rust-toolchain.toml b/examples/embassy-runtime-demo/rust-toolchain.toml new file mode 100644 index 00000000..b4f3adf4 --- /dev/null +++ b/examples/embassy-runtime-demo/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.90" +components = ["rust-src", "rustfmt", "llvm-tools"] +targets = ["thumbv8m.main-none-eabihf"] diff --git a/examples/embassy-runtime-demo/src/main.rs b/examples/embassy-runtime-demo/src/main.rs new file mode 100644 index 00000000..5bb63bb7 --- /dev/null +++ b/examples/embassy-runtime-demo/src/main.rs @@ -0,0 +1,92 @@ +#![no_std] +#![no_main] + +//! Example demonstrating full AimDB service integration with unified API + +use aimdb_core::{Database, RuntimeContext}; +use aimdb_embassy_adapter::{EmbassyAdapter, EmbassyDatabaseBuilder}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +// Simple embedded allocator (required by some dependencies) +#[global_allocator] +static ALLOCATOR: embedded_alloc::LlffHeap = embedded_alloc::LlffHeap::empty(); + +// Plain Embassy tasks - no #[service] macro! +// These wrap the shared implementations for clean separation + +#[embassy_executor::task] +async fn data_processor_task(adapter: &'static EmbassyAdapter) { + let ctx = RuntimeContext::new(adapter); + if let Err(e) = aimdb_examples_shared::data_processor_service(ctx).await { + error!("Data processor error: {:?}", defmt::Debug2Format(&e)); + } +} + +#[embassy_executor::task] +async fn monitoring_task(adapter: &'static EmbassyAdapter) { + let ctx = RuntimeContext::new(adapter); + if let Err(e) = aimdb_examples_shared::monitoring_service(ctx).await { + error!("Monitoring service error: {:?}", defmt::Debug2Format(&e)); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // Initialize heap for the allocator + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 8192; + static mut HEAP: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { + let heap_ptr = core::ptr::addr_of_mut!(HEAP); + ALLOCATOR.init((*heap_ptr).as_ptr() as usize, HEAP_SIZE) + } + } + + let p = embassy_stm32::init(Default::default()); + info!("🔧 Setting up AimDB with Embassy runtime..."); + + // Create database using the new unified builder API + // Use StaticCell to make db live for 'static + static DB_CELL: StaticCell = StaticCell::new(); + let db = DB_CELL.init(Database::::builder().build(spawner)); + + info!("✅ AimDB database created successfully"); + + // Setup LED for visual feedback + let mut led = Output::new(p.PB0, Level::High, Speed::Low); + + info!("🎯 Spawning services with unified API:"); + + let adapter_ref = db.adapter(); + + // Spawn services using Embassy tasks + spawner.spawn(data_processor_task(adapter_ref).unwrap()); + spawner.spawn(monitoring_task(adapter_ref).unwrap()); + + info!("⚡ Services spawned successfully!"); + + // Blink LED while services run + for i in 0..10 { + info!("LED blink {}/10", i + 1); + led.set_high(); + Timer::after(Duration::from_millis(250)).await; + + led.set_low(); + Timer::after(Duration::from_millis(250)).await; + } + + info!("🎉 All services completed successfully!"); + + // Keep LED on to signal completion + led.set_high(); + + loop { + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/examples/producer-consumer-demo/Cargo.toml b/examples/producer-consumer-demo/Cargo.toml new file mode 100644 index 00000000..6c21b610 --- /dev/null +++ b/examples/producer-consumer-demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "producer-consumer-demo" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "AimDB example demonstrating producer consumer creation based on a Tokio runtime" +publish = false + +[features] +default = ["std"] +std = [] + +[dependencies] +aimdb-core = { path = "../../aimdb-core" } +aimdb-tokio-adapter = { path = "../../aimdb-tokio-adapter" } +tokio = { version = "1.43", features = ["full"] } diff --git a/examples/producer-consumer-demo/src/main.rs b/examples/producer-consumer-demo/src/main.rs new file mode 100644 index 00000000..eb536718 --- /dev/null +++ b/examples/producer-consumer-demo/src/main.rs @@ -0,0 +1,204 @@ +//! Producer-Consumer Pattern Demo +//! +//! This example demonstrates the unified Database API in AimDB, +//! showing: +//! - Type-safe record registration with TypeId +//! - Self-registering records via RecordT trait +//! - Cross-record communication via Emitter +//! - Producer and consumer pipelines +//! - Built-in call tracking and observability +//! +//! # Architecture +//! +//! ```text +//! Messages (input) → Producer → Consumers → (may emit) → Alerts +//! ↓ +//! UpperConsumer +//! (if len > threshold) +//! ↓ +//! Alerts (output) +//! ``` + +use aimdb_core::{Database, Emitter, RecordRegistrar, RecordT}; +use aimdb_tokio_adapter::{TokioAdapter, TokioDatabaseBuilder}; +use std::sync::Arc; + +/* ================== Record Types ================== */ + +/// Represents incoming message data +#[derive(Clone, Debug)] +pub struct Messages(pub String); + +/// Represents alerts generated from messages +#[derive(Clone, Debug)] +pub struct Alerts(pub String); + +/* ================== Configuration ================== */ + +/// Shared configuration for message processing +#[derive(Clone)] +pub struct SharedStringsCfg { + pub prefix: String, + pub alert_threshold_len: usize, +} + +/// Configuration for message producer +#[derive(Clone)] +pub struct MsgProducer { + pub tag: String, + pub min_len: usize, +} + +impl MsgProducer { + pub async fn run(&self, _em: &Emitter, msg: &Messages) { + if msg.0.len() < self.min_len { + println!("[drop] {}", msg.0); + return; + } + println!("[{}] {}", self.tag, msg.0); + + // Simulate some async work + #[cfg(feature = "std")] + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } +} + +/// Consumer that converts messages to uppercase +#[derive(Clone)] +pub struct UpperConsumer; + +impl UpperConsumer { + pub async fn run(&self, em: &Emitter, shared: Arc, msg: &Messages) { + println!("{} [upper] {}", shared.prefix, msg.0.to_uppercase()); + + // Cross-emit: long messages raise an Alert + if msg.0.len() > shared.alert_threshold_len { + let _ = em.emit(Alerts(format!("[derived] {}", msg.0))).await; + } + } +} + +/* ================== Self-Registering Records ================== */ + +impl RecordT for Messages { + type Config = (Arc, Arc, Arc); + + fn register<'a>(reg: &'a mut RecordRegistrar<'a, Self>, cfg: &Self::Config) { + let (shared, prod, upper) = cfg.clone(); + + // Clone for closures + let prod_for_producer = prod.clone(); + let shared_for_consumer = shared.clone(); + let upper_for_consumer = upper.clone(); + + // Use fluent API - chain the calls + let _ = reg + .producer(move |em, msg| { + let prod = prod_for_producer.clone(); + async move { + prod.run(&em, &msg).await; + } + }) + .consumer(move |em, msg| { + let shared = shared_for_consumer.clone(); + let upper = upper_for_consumer.clone(); + async move { + upper.run(&em, shared, &msg).await; + } + }); + } +} + +impl RecordT for Alerts { + type Config = Arc; + + fn register<'a>(reg: &'a mut RecordRegistrar<'a, Self>, cfg: &Self::Config) { + let shared_for_consumer = cfg.clone(); + + // Use fluent API - chain the calls + let _ = reg + .producer(move |_em, a| async move { + println!("[ALERT] {}", a.0); + }) + .consumer(move |_em, a| { + let shared = shared_for_consumer.clone(); + async move { + println!("{} [sink] {}", shared.prefix, a.0); + } + }); + } +} + +/* ================== Main Demo ================== */ + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("╔══════════════════════════════════════════════════════════╗"); + println!("║ AimDB Producer-Consumer Pattern Demo (Tokio) ║"); + println!("╚══════════════════════════════════════════════════════════╝\n"); + + // Create configurations + let shared = Arc::new(SharedStringsCfg { + prefix: "[STR]".into(), + alert_threshold_len: 10, + }); + let prod = Arc::new(MsgProducer { + tag: "[sensor-A]".into(), + min_len: 3, + }); + let upper = Arc::new(UpperConsumer); + + println!("✓ Configuration created"); + + // Build database with self-registering records using the unified API + println!("Building database with type-safe records..."); + let db = Database::::builder() + .record::(&(shared.clone(), prod.clone(), upper.clone())) + .record::(&shared) + .build()?; + println!("✓ Database built successfully"); + + println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + println!("Emitting messages (data flows through pipeline):"); + + // Emit some messages - these will flow through the producer-consumer pipeline + db.produce(Messages("hi".into())).await?; + println!(); + + db.produce(Messages("hello".into())).await?; + println!(); + + db.produce(Messages("this one is definitely long".into())) + .await?; + println!(); + + // Give async tasks time to complete + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + + println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + println!("Inspecting call statistics:"); + + // Inspect producer statistics + if let Some((calls, last)) = db.producer_stats::() { + println!("📊 Messages producer:"); + println!(" • Calls: {}", calls); + println!(" • Last: {:?}", last); + } + + // Inspect consumer statistics + let consumers = db.consumer_stats::(); + for (i, (calls, last)) in consumers.into_iter().enumerate() { + println!("\n📊 Messages consumer #{}:", i + 1); + println!(" • Calls: {}", calls); + println!(" • Last: {:?}", last); + } + + // Inspect Alerts producer stats + if let Some((calls, last)) = db.producer_stats::() { + println!("\n📊 Alerts producer:"); + println!(" • Calls: {}", calls); + println!(" • Last: {:?}", last); + } + + Ok(()) +} diff --git a/examples/quickstart.rs b/examples/quickstart.rs deleted file mode 100644 index 8240ec77..00000000 --- a/examples/quickstart.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! AimDB Quick Start Example -//! -//! This is a simple demonstration of AimDB concepts. -//! Run with: cargo run --example quickstart - -fn main() { - println!("🚀 AimDB QuickStart Demo"); - println!("======================"); - - println!("📡 Initializing AimDB in-memory database..."); - println!("✅ Database initialized"); - - println!("\n🏭 Simulating MCU device data:"); - for i in 1..=3 { - println!(" 📊 MCU Device #{} - Temperature: {}°C", i, 20 + i * 2); - } - - println!("\n🌐 Edge gateway processing:"); - println!(" 🔄 Processing sensor data..."); - println!(" 📈 Average temperature: 24°C"); - - println!("\n☁️ Cloud synchronization:"); - println!(" 📤 Data uploaded to cloud"); - println!(" ✅ All devices synchronized"); - - println!("\n✨ Demo completed! AimDB keeps MCU → edge → cloud in sync."); -} diff --git a/examples/shared/Cargo.toml b/examples/shared/Cargo.toml new file mode 100644 index 00000000..b81d4714 --- /dev/null +++ b/examples/shared/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "aimdb-examples-shared" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Shared code for AimDB examples and demos" +publish = false + +[features] +default = ["std"] +std = ["aimdb-core/std", "aimdb-executor/std"] +embassy-runtime = [ + "aimdb-core/embassy-runtime", + "aimdb-executor/embassy-types", + "dep:defmt", +] +tokio-runtime = ["std", "aimdb-core/tokio-runtime"] + +[dependencies] +aimdb-core = { path = "../../aimdb-core", default-features = false } +aimdb-executor = { path = "../../aimdb-executor", default-features = false } +defmt = { version = "0.3", optional = true } diff --git a/examples/shared/src/lib.rs b/examples/shared/src/lib.rs new file mode 100644 index 00000000..a970fd60 --- /dev/null +++ b/examples/shared/src/lib.rs @@ -0,0 +1,91 @@ +//! Shared service implementations for AimDB examples +//! +//! These services are runtime-agnostic and can be used with any Runtime implementation +//! (TokioAdapter, EmbassyAdapter, etc.) +//! + +#![cfg_attr(not(feature = "std"), no_std)] + +use aimdb_core::{DbResult, RuntimeContext}; +use aimdb_executor::Runtime; + +/// Background data processing service +/// +/// Demonstrates runtime-agnostic service that processes data in batches. +/// Generic over any Runtime implementation. +/// +/// This service demonstrates the clean accessor API: +/// - Store accessors at the beginning: `let log = ctx.log(); let time = ctx.time();` +/// - Use them throughout the service for clean, efficient code +pub async fn data_processor_service(ctx: RuntimeContext) -> DbResult<()> { + // Store accessors for reuse throughout the service + let log = ctx.log(); + let time = ctx.time(); + + log.info("🚀 Data processor service started"); + + for i in 1..=5 { + match i { + 1 => log.info("📊 Processing batch 1/5"), + 2 => log.info("📊 Processing batch 2/5"), + 3 => log.info("📊 Processing batch 3/5"), + 4 => log.info("📊 Processing batch 4/5"), + 5 => log.info("📊 Processing batch 5/5"), + _ => {} + } + + // Clean time operations using stored accessor + time.sleep(time.millis(200)).await; + + match i { + 1 => log.info("✅ Batch 1 completed"), + 2 => log.info("✅ Batch 2 completed"), + 3 => log.info("✅ Batch 3 completed"), + 4 => log.info("✅ Batch 4 completed"), + 5 => log.info("✅ Batch 5 completed"), + _ => {} + } + } + + log.info("🏁 Data processor service completed"); + + Ok(()) +} + +/// Monitoring and health check service +/// +/// Demonstrates runtime-agnostic service that performs periodic health checks. +/// Measures timing using the runtime context. +/// +/// This service demonstrates the clean accessor API with timing measurements. +/// Accessors are stored once and reused throughout the service. +pub async fn monitoring_service(ctx: RuntimeContext) -> DbResult<()> { + // Store accessors at the beginning for clean, efficient code + let log = ctx.log(); + let time = ctx.time(); + + log.info("📈 Monitoring service started"); + + for i in 1..=3 { + let start_time = time.now(); + + match i { + 1 => log.info("🔍 Health check 1/3"), + 2 => log.info("🔍 Health check 2/3"), + 3 => log.info("🔍 Health check 3/3"), + _ => {} + } + + // Clean time operations using stored accessor + time.sleep(time.millis(150)).await; + + let end_time = time.now(); + let _duration = time.duration_since(end_time, start_time).unwrap(); + + log.info("💚 System healthy"); + } + + log.info("📈 Monitoring service completed"); + + Ok(()) +} diff --git a/examples/tokio-runtime-demo/Cargo.toml b/examples/tokio-runtime-demo/Cargo.toml new file mode 100644 index 00000000..ae445db6 --- /dev/null +++ b/examples/tokio-runtime-demo/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "aimdb-tokio-demo" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "AimDB example demonstrating Tokio runtime integration" +publish = false + +[features] +default = ["tokio-runtime"] +tokio-runtime = [] +embassy-runtime = [] + +[dependencies] +# Shared examples library +aimdb-examples-shared = { path = "../shared", features = [ + "std", + "tokio-runtime", +] } + +# Core AimDB dependencies +aimdb-core = { path = "../../aimdb-core", features = ["std", "tokio-runtime"] } +aimdb-executor = { path = "../../aimdb-executor", features = ["std"] } +aimdb-tokio-adapter = { path = "../../aimdb-tokio-adapter", features = [ + "tokio-runtime", + "tracing", +] } +# aimdb-macros = { path = "../../aimdb-macros", features = ["tokio-runtime"] } # No longer used + +# Tokio runtime +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } diff --git a/examples/tokio-runtime-demo/src/main.rs b/examples/tokio-runtime-demo/src/main.rs new file mode 100644 index 00000000..4e628cf5 --- /dev/null +++ b/examples/tokio-runtime-demo/src/main.rs @@ -0,0 +1,54 @@ +//! Example demonstrating full AimDB service integration with unified API + +use aimdb_core::{Database, DbResult, RuntimeContext}; +use aimdb_executor::Runtime; +use aimdb_tokio_adapter::{TokioAdapter, TokioDatabaseBuilder}; +use std::time::Duration; + +// These wrap the shared implementations for clean separation +async fn data_processor_service(ctx: RuntimeContext) -> DbResult<()> { + aimdb_examples_shared::data_processor_service(ctx).await +} + +async fn monitoring_service(ctx: RuntimeContext) -> DbResult<()> { + aimdb_examples_shared::monitoring_service(ctx).await +} + +#[tokio::main] +async fn main() -> DbResult<()> { + println!("🔧 Setting up AimDB with Tokio runtime..."); + + // Create database using the new unified builder API + let db = Database::::builder().build()?; + + println!("✅ AimDB database created successfully"); + + // Get runtime context for services + let ctx = db.context(); + + println!("\n🎯 Spawning services with unified API:"); + + // Spawn services using the unified spawn() method + // This works the same way across both Tokio and Embassy! + let ctx1 = ctx.clone(); + db.spawn(async move { + if let Err(e) = data_processor_service(ctx1).await { + eprintln!("Data processor error: {:?}", e); + } + })?; + + let ctx2 = ctx.clone(); + db.spawn(async move { + if let Err(e) = monitoring_service(ctx2).await { + eprintln!("Monitoring service error: {:?}", e); + } + })?; + + println!("\n⚡ Services spawned successfully!"); + + // Give the services some time to run + tokio::time::sleep(Duration::from_secs(2)).await; + + println!("\n🎉 All services completed successfully!"); + Ok(()) +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..556f3407 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +# AimDB rustfmt configuration + +# Standard formatting rules +edition = "2024"