From 7f8769e02caa541c5e648b29b58ad72199d83142 Mon Sep 17 00:00:00 2001 From: core Date: Fri, 28 Nov 2025 22:25:47 -0500 Subject: [PATCH] feat: switch solver --- Cargo.lock | 329 ++++++++++++++------ crates/unified/Cargo.toml | 10 +- crates/unified/src/client/ship/thrusters.rs | 236 ++++++++++---- 3 files changed, 405 insertions(+), 170 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 709926833b887672eed5b493b1ae3d2bd1d312b5..822904ebe3c43347e4751909fa369dc50c4e3fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "amd" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a679e001575697a3bd195813feb57a4718ecc08dc194944015cbc5f6213c2b96" +dependencies = [ + "num-traits", +] + [[package]] name = "android-activity" version = "0.6.0" @@ -422,7 +431,7 @@ checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -529,7 +538,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -567,7 +576,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -683,7 +692,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -845,7 +854,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -941,7 +950,7 @@ checksum = "b8c733807158f8fcac68e23222e69ed91a6492ae9410fc2c145b9bb182cfd63e" dependencies = [ "bevy_macro_utils", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1034,7 +1043,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1146,7 +1155,7 @@ checksum = "714273aa7f285c0aaa874b7fbe37fe4e6e45355e3e6f3321aefa1b78cda259e0" dependencies = [ "bevy_macro_utils", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1322,7 +1331,7 @@ dependencies = [ "parking_lot", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "toml_edit", ] @@ -1456,7 +1465,7 @@ dependencies = [ "spin", "wasm-bindgen", "wasm-bindgen-futures", - "web-time", + "web-time 1.1.0", ] [[package]] @@ -1532,7 +1541,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "uuid", ] @@ -1594,7 +1603,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1735,7 +1744,7 @@ checksum = "5c3e4e32b1b96585740a2b447661af7db1b9d688db5e4d96da50461cd8f5ce63" dependencies = [ "bevy_macro_utils", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1963,7 +1972,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn", + "syn 2.0.110", ] [[package]] @@ -2089,7 +2098,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2292,6 +2301,26 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "clarabel" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d950c870fad4feed732bc3f7da489d223ba3911eac0522e6512eed7e659cfafe" +dependencies = [ + "amd", + "cfg-if", + "derive_builder", + "enum_dispatch", + "itertools 0.11.0", + "lazy_static", + "num-traits", + "serde", + "serde-big-array", + "serde_json", + "thiserror 1.0.69", + "web-time 0.2.4", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -2672,6 +2701,41 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -2693,6 +2757,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -2710,7 +2805,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "unicode-xid", ] @@ -2837,7 +2932,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2956,7 +3051,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "web-time", + "web-time 1.1.0", "winapi", "windows-sys 0.59.0", "winit", @@ -3012,7 +3107,7 @@ dependencies = [ "profiling", "thiserror 1.0.69", "type-map", - "web-time", + "web-time 1.1.0", "wgpu 25.0.2", "winit", ] @@ -3032,7 +3127,7 @@ dependencies = [ "profiling", "raw-window-handle", "smithay-clipboard", - "web-time", + "web-time 1.1.0", "webbrowser", "winit", ] @@ -3148,7 +3243,7 @@ checksum = "1796db3d892515842ca2dfb11124c4bb4a9e58d9f2c5c1072e5bca1b2334507b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3174,7 +3269,19 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -3195,7 +3302,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3206,7 +3313,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3343,7 +3450,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3460,7 +3567,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3562,7 +3669,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3917,6 +4024,16 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "good_lp" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "776aa1ba88ac058e78408c17f4dbff826a51ae08ed6642f71ca0edd7fe9383f3" +dependencies = [ + "clarabel", + "fnv", +] + [[package]] name = "gpu-alloc" version = "0.6.0" @@ -4195,6 +4312,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -4320,6 +4443,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -4491,7 +4623,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -4706,16 +4838,6 @@ dependencies = [ "paste", ] -[[package]] -name = "microlp" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d1790c73b93164ff65868f63164497cb32339458a9297e17e212d91df62258" -dependencies = [ - "log", - "sprs", -] - [[package]] name = "mime" version = "0.3.17" @@ -4882,22 +5004,7 @@ checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "ndarray" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7c9125e8f6f10c9da3aad044cc918cf8784fa34de857b1aa68038eb05a50a9" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rawpointer", + "syn 2.0.110", ] [[package]] @@ -5054,7 +5161,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5106,7 +5213,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5627,7 +5734,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "unicase", ] @@ -5664,7 +5771,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5798,7 +5905,7 @@ checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5847,7 +5954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.110", ] [[package]] @@ -5878,7 +5985,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6453,6 +6560,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_arrays" version = "0.2.0" @@ -6479,7 +6595,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6513,7 +6629,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6743,18 +6859,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "sprs" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dca58a33be2188d4edc71534f8bafa826e787cc28ca1c47f31be3423f0d6e55" -dependencies = [ - "ndarray", - "num-complex", - "num-traits", - "smallvec", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -6784,8 +6888,8 @@ dependencies = [ "console_error_panic_hook", "ctrlc", "getrandom 0.3.4", + "good_lp", "leafwing-input-manager", - "microlp", "ordered-float 5.1.0", "pico-args", "rand 0.9.2", @@ -6813,6 +6917,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.26.3" @@ -6832,7 +6942,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.110", ] [[package]] @@ -6896,6 +7006,17 @@ dependencies = [ "zeno", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.110" @@ -6915,7 +7036,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6969,7 +7090,7 @@ checksum = "2de773517ee4367314c7918f6c9ef69c201ba72bfdbffb00234c22c50a153b73" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "unicode-ident", ] @@ -6999,7 +7120,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -7010,7 +7131,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -7227,7 +7348,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -7390,7 +7511,7 @@ checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -7570,7 +7691,7 @@ checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -7620,7 +7741,7 @@ checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -7683,7 +7804,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -7841,6 +7962,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -8329,7 +8460,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -8340,7 +8471,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -8351,7 +8482,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -8362,7 +8493,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -8727,7 +8858,7 @@ dependencies = [ "wayland-protocols", "wayland-protocols-plasma", "web-sys", - "web-time", + "web-time 1.1.0", "windows-sys 0.52.0", "x11-dl", "x11rb", @@ -8872,7 +9003,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "synstructure", ] @@ -8928,7 +9059,7 @@ checksum = "dc6821851fa840b708b4cbbaf6241868cabc85a2dc22f426361b0292bfc0b836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "zbus-lockstep", "zbus_xml", "zvariant", @@ -8943,7 +9074,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "zbus_names", "zvariant", "zvariant_utils", @@ -8997,7 +9128,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -9017,7 +9148,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "synstructure", ] @@ -9057,7 +9188,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -9098,7 +9229,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "zvariant_utils", ] @@ -9111,6 +9242,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn", + "syn 2.0.110", "winnow", ] diff --git a/crates/unified/Cargo.toml b/crates/unified/Cargo.toml index 14b85c3b89707545ae41e850c3a3b36b89fe7a05..1334e30b8541886649a205c7e3a9651d334d6e9c 100644 --- a/crates/unified/Cargo.toml +++ b/crates/unified/Cargo.toml @@ -75,7 +75,7 @@ pico-args = "0.5" bevy_egui = { version = "0.38", optional = true } leafwing-input-manager = { version = "0.19", optional = true } -microlp = { version = "0.2", optional = true } +good_lp = { version = "1.14", default-features = false, features = ["clarabel"], optional = true } [build-dependencies] built = { version = "0.8", features = ["git2", "chrono"] } @@ -95,13 +95,13 @@ native = [ "bevy/dynamic_linking", "bevy/x11", "bevy/wayland", - "ctrlc" + "dep:ctrlc" ] wasm = ["getrandom/wasm_js", "bevy/webgl2"] -particle_editor = ["bevy_egui"] +particle_editor = ["dep:bevy_egui"] server = ["aeronet_websocket/server", "aeronet_replicon/server"] client = [ - "leafwing-input-manager", - "microlp" + "dep:leafwing-input-manager", + "dep:good_lp" ] \ No newline at end of file diff --git a/crates/unified/src/client/ship/thrusters.rs b/crates/unified/src/client/ship/thrusters.rs index 8ce72d50571f31baee4f774f8b9721a399c66285..51147169510cae07ef49318675ef97456bf93c71 100644 --- a/crates/unified/src/client/ship/thrusters.rs +++ b/crates/unified/src/client/ship/thrusters.rs @@ -4,8 +4,8 @@ use bevy::app::App; use bevy::color::palettes::basic::{RED, WHITE}; use bevy::color::palettes::css::LIMEGREEN; use bevy::math::Vec3Swizzles; +use good_lp::{default_solver, variable, Expression, ProblemVariables, Solution, SolutionStatus, SolverModel, Variable}; use leafwing_input_manager::prelude::ActionState; -use microlp::{OptimizationDirection, Problem}; use crate::attachment::Parts; use crate::client::input::ClientAction; use crate::ecs::thruster::{PartThrusters, Thruster, ThrusterOfPart}; @@ -56,6 +56,8 @@ fn draw_thruster_debug( } // TODO: split this into two passes +/// The thrust solver! +/// This is an annoyingly complicated function... fn solve_thrust( me: Query<(Option<&Parts>, &GlobalTransform, Entity), With>, parts: Query<&PartThrusters>, @@ -78,6 +80,7 @@ fn solve_thrust( solution.thrusters_on.clear(); solution.converged = false; + // we need to find our entire ship let Ok((our_parts, hearty_transform, hearty)) = me.single() else { error!("could not solve for thrust: hearty does not exist?"); error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); @@ -91,31 +94,53 @@ fn solve_thrust( // +Z == counterclockwise/ccw // -Z == clockwise/cw - let mut target_unit_vector = Vec3::ZERO; - let mut target_torque_vector = Vec3::ZERO; - let mut anything_pressed = false; + /* + Background info: + + The thrust solver operates in two passes. + + Pass 1: confusingly called the "Thrust" pass, is responsible for cartesian thrust vectoring + Pass 2: appropriately named the "Torque" pass, is soley responsible for torque + + In the end, the results of both passes are added together and set as the thrust solution. + + This is why this whole function operates in duplicate. + + */ + + let mut target_unit_vector = Vec3::ZERO; // target vector for thrust pass + let mut target_torque_vector = Vec3::ZERO; // target vector for torque pass + + let mut anything_pressed = false; // are we going to do anything? + + // +y if input.pressed(&ClientAction::ThrustForward) { anything_pressed = true; target_unit_vector += hearty_transform.rotation() * Vec3::new(0.0, 1.0, 0.0); } + // -y if input.pressed(&ClientAction::ThrustBackward) { anything_pressed = true; target_unit_vector += hearty_transform.rotation() * Vec3::new(0.0, -1.0, 0.0); } + // +x if input.pressed(&ClientAction::ThrustRight) { anything_pressed = true; target_unit_vector += hearty_transform.rotation() * Vec3::new(1.0, 0.0, 0.0); } + // -x if input.pressed(&ClientAction::ThrustLeft) { anything_pressed = true; target_unit_vector += hearty_transform.rotation() * Vec3::new(-1.0, 0.0, 0.0); } + // cw => -z if input.pressed(&ClientAction::TorqueCw) { anything_pressed = true; target_torque_vector += Vec3::new(0.0, 0.0, -1.0); } + // ccw => +z if input.pressed(&ClientAction::TorqueCcw) { anything_pressed = true; target_torque_vector += Vec3::new(0.0, 0.0, 1.0); @@ -125,19 +150,28 @@ fn solve_thrust( trace!("no buttons are pressed; zeroing thrust solution"); trace!("solved thrust in {}ms", start.elapsed().as_millis()); solution.converged = true; - events.write(solution.clone()); + events.write(solution.clone()); // send our solution to the server, to be applied return; } + // Normalize the target vectors. + // The thrust solver operates purely based on direction; + // it does not care about the strength of the thrusters. + // Thus, we normalize everything; + // including the target unit vectors, which are rotated with respect to Hearty if target_unit_vector != Vec3::ZERO { target_unit_vector = target_unit_vector.normalize(); } + // TODO(core): the torque vector should already be normalized, but the solver breaks without this. + // TODO(core): Investigate if target_torque_vector != Vec3::ZERO { target_torque_vector = target_torque_vector.normalize(); } + // Determine all parts on the ship. It contains at least Hearty... let mut all_parts = vec![hearty]; if let Some(parts) = our_parts { + // and if we have an &Parts, all the attached parts too all_parts.extend(parts.iter()); } @@ -146,7 +180,7 @@ fn solve_thrust( for part in &all_parts { let Ok(part_thrusters) = parts.get(*part) else { - continue; + continue; // This part has no thrusters }; for thruster_id in &**part_thrusters { let Ok((thruster, thruster_transform)) = thrusters.get(*thruster_id) else { @@ -159,22 +193,28 @@ fn solve_thrust( // determine our xy offset from hearty let relative_translation = thruster_transform.translation().xy() - hearty_transform.translation().xy(); - // determine our rotational offset from hearty - let relative_rotation = thruster_transform.rotation() * -hearty_transform.rotation(); + // Magic torque equation: I stole this from avian's code + // The only difference is that like everything else, it's all normalized + // I haven't the faintest idea what this actually does + // No touchy let thruster_torque = relative_translation.normalize().extend(0.0).cross(thruster_vector.normalize().extend(0.0)).z; + + // Although all the numbers going in were normalized, the torque output is in different + // units and is wacky. Re-normalize it to a set of expected values, since this is all + // direction based anyway. let renormalized_thruster_torque = if thruster_torque.abs() < 0.1 { - 0.0 + 0.0 // This thruster's effect is small enough to be ignored } else if thruster_torque < 0.0 { - -1.0 + -1.0 // if it's negative, force to -1 } else { - 1.0 + 1.0 // if it's positive, force to +1 }; + // TODO(core): remove overly verbose debug logging trace!("thruster: {:?} {}({})", thruster_vector, thruster_torque, renormalized_thruster_torque); - // magically assemble the worldspace vector! for the solver (not shipspace) - + // Then, push all this data for the next section to deal with. all_thrusters.push((thruster_id, thruster_vector.extend(0.0), Vec3::new(0.0, 0.0, renormalized_thruster_torque) @@ -182,99 +222,157 @@ fn solve_thrust( } } + /* + Why are we normalizing everything, you may ask? + + A: The thrust solver concerns itself only with direction. It is intended to be a more dynamic + alternative to the standard "assume the ship is the structure it should be and guess the thrust + offsets from that" approach, mostly because I didn't want to implement that, and this seemed + more fun. + + Also, it makes the solver converge faster, because reasons. + */ + // calculate thrust ~~and torque~~ values + + /* + Consult the paper for more information. + + Recall that we're optimizing an equation of form i_0 * x_0 + i_1 * x_1 + i_2 * x_2 ... i_n * x_n + "Coefficients" are i_0 ... i_n, and can be precomputed, and x_0 ... x_n is the "decision variables" + */ + + // TODO(core): Remove overly verbose debug logging trace!("found {} thrusters, computing coefficients", all_thrusters.len()); + if all_thrusters.len() == 0 { + trace!("there are no thrusters; zeroing thrust solution"); + trace!("solved thrust in {}ms", start.elapsed().as_millis()); + solution.converged = true; + events.write(solution.clone()); // send our solution to the server, to be applied + return; + } + + // TODO(core): Remove overly verbose debug logging for thruster in &all_thrusters { trace!("thruster on ship: {:?}", thruster); } let coefficients = all_thrusters.iter() .map(|u| { + // TODO(core): Remove overly verbose debug logging trace!("{} dot {}, {} dot {}", target_unit_vector, u.1.normalize(), target_torque_vector, u.2.normalize()); + // Computes both system coefficients, for simplicity ( - target_unit_vector.dot(u.1.normalize()), - target_torque_vector.dot(u.2.normalize()) + target_unit_vector.dot(u.1.normalize()), // Thrust coefficient + target_torque_vector.dot(u.2.normalize()) // Torque coefficient ) }) .map(|u| { + // TODO(core): Remove overly verbose debug logging trace!("=> {}, {}", u.0, u.1); + // improve reliability: - // if dot is <0.1, zap it entirely (this thruster is not helping) - // TODO: figure out how to make this adjustable + // if thrust coefficient is <0.1, zap it entirely (this thruster is not helping) + // This is done elsewhere for torque, so pass it (u.1) through unchanged + // TODO(core): figure out how to make this adjustable if u.0.abs() < 0.1 { (0.0, u.1) } else { (u.0, u.1) } }) - //.map(|u| u.powi(3)) + .map(|u| { + // Sometimes NaN shows up. This just means zero. I hate math. + ( + if u.0.is_nan() { 0.0 } else { u.0 }, + if u.1.is_nan() { 0.0 } else { u.1 }, + ) + }) .collect::>(); trace!("preparing models"); - let mut thrust_problem = Problem::new(OptimizationDirection::Maximize); - let mut torque_problem = Problem::new(OptimizationDirection::Maximize); + /* The Model is the actual solver. Currently using clarabel, but this could change. */ + let mut thrust_variables = ProblemVariables::new(); + let mut torque_variables = ProblemVariables::new(); // add variables to problem + // Iterate through each of our variables (thrusters) and add them to the model. + // This will be used next to create the actual problem. let variables = coefficients.iter() .map(|u| { + // We need to return these handles later to get the values back ( - thrust_problem.add_var(u.0 as f64, (0.0, 1.0)), - torque_problem.add_var(u.1 as f64, (0.0, 1.0)) + ( + u.0 as f64, + thrust_variables.add(variable().min(0.0).max(1.0).initial(u.0)) + ), + ( + u.1 as f64, + torque_variables.add(variable().min(0.0).max(1.0).initial(u.1)) + ), ) }) .collect::>(); + // Calculate the actual problem; this is a bounded sum + let thrust_problem: Expression = variables.iter().map(|u| u.0.0 * u.0.1).sum(); + let torque_problem: Expression = variables.iter().map(|u| u.1.0 * u.1.1).sum(); + trace!("prepared {} variables; solving", variables.len()); - let thrust_solution = match thrust_problem.solve() { + // now, we run the actual solver! + + trace!("starting thrust solve @ {:?}", start.elapsed()); + + let thrust_solution = match thrust_variables.maximise(thrust_problem).using(default_solver).solve() { Ok(soln) => soln, Err(e) => { - match e { - microlp::Error::Infeasible => { - error!("failed to solve for thrust: constraints cannot be satisfied"); - error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); - return; - }, - microlp::Error::Unbounded => { - error!("failed to solve for thrust: system is unbounded"); - error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); - return; - }, - microlp::Error::InternalError(e) => { - error!("failed to solve for thrust: solver encountered internal error: {e}"); - error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); - return; - } - } + error!("failed to solve for thrust: {}", e.to_string()); + error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); + return; } }; - let torque_solution = match torque_problem.solve() { + + // did the solution converge? + match thrust_solution.status() { + SolutionStatus::Optimal => {}, // yay! + SolutionStatus::TimeLimit => { + warn!("thrust solver failed to converge, hit time limit") + } + SolutionStatus::GapLimit => { + warn!("thrust solver failed to converge, hit gap limit") + } + } + + trace!("finished thrust solve @ {:?}", start.elapsed()); + trace!("starting torque solve @ {:?}", start.elapsed()); + + let torque_solution = match torque_variables.maximise(torque_problem).using(default_solver).solve() { Ok(soln) => soln, Err(e) => { - match e { - microlp::Error::Infeasible => { - error!("failed to solve for torque: constraints cannot be satisfied"); - error!("failed to solve for torque after {}ms", start.elapsed().as_millis()); - return; - }, - microlp::Error::Unbounded => { - error!("failed to solve for torque: system is unbounded"); - error!("failed to solve for torque after {}ms", start.elapsed().as_millis()); - return; - }, - microlp::Error::InternalError(e) => { - error!("failed to solve for torque: solver encountered internal error: {e}"); - error!("failed to solve for torque after {}ms", start.elapsed().as_millis()); - return; - } - } + error!("failed to solve for torque: {}", e.to_string()); + error!("failed to solve for torque after {}ms", start.elapsed().as_millis()); + return; } }; + // did the solution converge? + match torque_solution.status() { + SolutionStatus::Optimal => {}, // yay! + SolutionStatus::TimeLimit => { + warn!("torque solver failed to converge, hit time limit") + } + SolutionStatus::GapLimit => { + warn!("torque solver failed to converge, hit gap limit") + } + } + + trace!("finished torque solve @ {:?}ms", start.elapsed()); + trace!("found thrust+torque solution!"); - trace!("thrust solution alignment (higher is better): {}", thrust_solution.objective()); - trace!("torque solution alignment (higher is better): {}", torque_solution.objective()); + + // Finally, extract the info out of the models and compile it into a cohesive ThrustSolution. let mut new_soln = ThrustSolution { thrusters_on: BTreeSet::default(), @@ -282,17 +380,23 @@ fn solve_thrust( }; for thruster in all_thrusters.iter().enumerate() { - trace!("thrust solution: thruster #{} ({:?}): {} @ coeff {}", thruster.0, thruster.1.0, thrust_solution.var_value(variables[thruster.0].0), coefficients[thruster.0].0); - trace!("torque solution: thruster #{} ({:?}): {} @ coeff {}", thruster.0, thruster.1.0, torque_solution.var_value(variables[thruster.0].1), coefficients[thruster.0].1); - // TODO: make this more easily adjustable - if *thrust_solution.var_value(variables[thruster.0].0) > 0.8 || *torque_solution.var_value(variables[thruster.0].1) > 0.8 { + // TODO(core): Remove overly verbose debug logging + trace!("thrust solution: thruster #{} ({:?}): {} @ coeff {}", thruster.0, thruster.1.0, thrust_solution.value(variables[thruster.0].0.1), coefficients[thruster.0].0); + trace!("torque solution: thruster #{} ({:?}): {} @ coeff {}", thruster.0, thruster.1.0, torque_solution.value(variables[thruster.0].1.1), coefficients[thruster.0].1); + // TODO(core): make this more easily adjustable + + // Currently, we only turn on a thruster if it's variable value (think weight in a weighted sum) + // is above 80%. + // The solver seems to be picking 0.0 or 1.0 in all circumstances anyway, but just in case. + + if thrust_solution.value(variables[thruster.0].0.1) > 0.8 || torque_solution.value(variables[thruster.0].1.1) > 0.8 { new_soln.thrusters_on.insert(*thruster.1.0); } } let elapsed = start.elapsed(); debug!(?elapsed, ?target_unit_vector, ?target_torque_vector, "solved for thrust and torque"); - *solution = new_soln; - events.write(solution.clone()); + *solution = new_soln; // save it to the Resource for use on the client... + events.write(solution.clone()); // ...then send it to the server! return; }