first commit, not pretty
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.direnv
|
||||
818
Cargo.lock
generated
Normal file
818
Cargo.lock
generated
Normal file
@@ -0,0 +1,818 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[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"
|
||||
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",
|
||||
]
|
||||
|
||||
[[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 = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[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.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minifb"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c470a74618b43cd182c21b3dc1e6123501249f3bad9a0085e95d1304ca2478"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"dlib",
|
||||
"futures",
|
||||
"instant",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"orbclient",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"tempfile",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-client",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"winapi",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "orbclient"
|
||||
version = "0.3.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "247ad146e19b9437f8604c21f8652423595cf710ad108af40e77d3ae6e96b827"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libredox",
|
||||
"sdl2",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[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 = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[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-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.35.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[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 = "syn"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "teleprof"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"minifb",
|
||||
"once_cell",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"downcast-rs",
|
||||
"libc",
|
||||
"nix",
|
||||
"scoped-tls",
|
||||
"wayland-commons",
|
||||
"wayland-scanner",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-commons"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"wayland-client",
|
||||
"wayland-commons",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"lazy_static",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "teleprof"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
minifb = "0.27"
|
||||
crossbeam-channel = "0.5"
|
||||
once_cell = "1.19"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
minifb = "0.27"
|
||||
141
README.md
Normal file
141
README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Teleprof
|
||||
|
||||
A lightweight, debug-only telemetry profiler for Rust applications. Shows thread activity and call stack hierarchy in real-time.
|
||||
|
||||
Inspired by RAD Telemetry - built in ~400 LOC with minimal dependencies.
|
||||
|
||||
## Features
|
||||
|
||||
- **Icicle graph** showing call stack hierarchy (top half)
|
||||
- **Thread timeline** showing per-thread activity over time (bottom half)
|
||||
- **Monokai color palette** for easy visual distinction
|
||||
- **Pause mechanism** to freeze your application for inspection
|
||||
- **Ringbuffer storage** (~16MB, 1M events) for recent history
|
||||
- **Lock-free event recording** via MPSC channels
|
||||
|
||||
## Dependencies
|
||||
|
||||
Only 3 dependencies (~15 total including transitive):
|
||||
- `minifb` - Window and framebuffer
|
||||
- `crossbeam-channel` - Lock-free MPSC
|
||||
- `once_cell` - Lazy statics
|
||||
|
||||
## Usage
|
||||
|
||||
### Add to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
teleprof = { path = "../teleprof" } # or from crates.io when published
|
||||
```
|
||||
|
||||
### In your code:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// Start the profiler window (separate thread)
|
||||
#[cfg(debug_assertions)]
|
||||
teleprof::start();
|
||||
|
||||
// Your application code
|
||||
game_loop();
|
||||
}
|
||||
|
||||
fn game_loop() {
|
||||
loop {
|
||||
// Profile a scope
|
||||
teleprof::span!("game_loop");
|
||||
|
||||
update();
|
||||
render();
|
||||
|
||||
// Check if paused (optional)
|
||||
if teleprof::PAUSE.try_lock().is_err() {
|
||||
// Wait until unpaused
|
||||
while teleprof::PAUSE.try_lock().is_err() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update() {
|
||||
teleprof::span!("update");
|
||||
// Your update code
|
||||
}
|
||||
|
||||
fn render() {
|
||||
teleprof::span!("render");
|
||||
// Your render code
|
||||
}
|
||||
```
|
||||
|
||||
### For closures:
|
||||
|
||||
```rust
|
||||
let work = || {
|
||||
teleprof::span!("my_closure");
|
||||
// work...
|
||||
};
|
||||
```
|
||||
|
||||
## Controls
|
||||
|
||||
- **Space**: Toggle pause (acquires `PAUSE` lock to freeze your app)
|
||||
- **Escape**: Close profiler window
|
||||
|
||||
## How it works
|
||||
|
||||
1. `span!()` macro creates a `SpanGuard` that sends `SpanStart` on creation
|
||||
2. When the guard drops, sends `SpanEnd`
|
||||
3. Events are sent via lock-free MPSC channel
|
||||
4. Window thread drains events into a fixed-size ringbuffer
|
||||
5. Renders icicle graph (call hierarchy) and timeline (per-thread activity)
|
||||
|
||||
## Design Goals
|
||||
|
||||
- **Minimal overhead**: Lock-free event recording
|
||||
- **Debug-only**: Compile out in release builds with `#[cfg(debug_assertions)]`
|
||||
- **Separate window**: Doesn't interfere with your app's rendering
|
||||
- **Simple API**: Just `span!("name")` and you're done
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Icicle Graph (Call Stack) │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ frame_work │ │
|
||||
│ ├──────────┬───────────────┤ │
|
||||
│ │ physics │ render │ │
|
||||
│ ├────┬─────┤ │ │
|
||||
│ │ w0 │ w1 │ │ │
|
||||
│ └────┴─────┴───────────────┘ │
|
||||
├─────────────────────────────────────┤
|
||||
│ Thread Timeline │
|
||||
│ Main: ████████████████████ │
|
||||
│ Work 0: ░░██████░░░░░░░░░░░ │
|
||||
│ Work 1: ░░░░░░██████░░░░░░░ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Run the included examples:
|
||||
|
||||
```bash
|
||||
# Multi-threaded physics simulation
|
||||
cargo run --example demo
|
||||
|
||||
# Bouncing ball with color-picking thread (30 FPS)
|
||||
cargo run --example bouncing_ball
|
||||
```
|
||||
|
||||
The bouncing ball example demonstrates:
|
||||
- Main thread running at 30 FPS with clear frame gaps
|
||||
- Background thread spawned on wall collision to pick colors
|
||||
- Clear visual separation between thread activities
|
||||
|
||||
## License
|
||||
|
||||
MIT / Apache-2.0 (choose whichever you prefer)
|
||||
204
examples/bouncing_ball.rs
Normal file
204
examples/bouncing_ball.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use rand::Rng;
|
||||
|
||||
const WIDTH: usize = 800;
|
||||
const HEIGHT: usize = 600;
|
||||
const BALL_RADIUS: usize = 20;
|
||||
|
||||
// Simple ball state
|
||||
struct Ball {
|
||||
x: f32,
|
||||
y: f32,
|
||||
vx: f32,
|
||||
vy: f32,
|
||||
color: u32,
|
||||
}
|
||||
|
||||
impl Ball {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
x: 400.0,
|
||||
y: 300.0,
|
||||
vx: 200.0, // pixels per second
|
||||
vy: 150.0,
|
||||
color: 0xFF6464FF, // Red-ish
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Start the telemetry window
|
||||
teleprof::start();
|
||||
|
||||
println!("Bouncing Ball Demo");
|
||||
println!("The ball window should appear alongside the profiler");
|
||||
println!("Press Space in profiler window to pause");
|
||||
println!("Press Escape in either window to quit");
|
||||
println!();
|
||||
|
||||
let mut window = Window::new(
|
||||
"Bouncing Ball",
|
||||
WIDTH,
|
||||
HEIGHT,
|
||||
WindowOptions::default(),
|
||||
)
|
||||
.expect("Failed to create window");
|
||||
|
||||
window.set_target_fps(30);
|
||||
|
||||
let ball = Arc::new(Mutex::new(Ball::new()));
|
||||
let mut framebuffer = vec![0u32; WIDTH * HEIGHT];
|
||||
|
||||
// Target 30 FPS
|
||||
let frame_time = Duration::from_millis(33);
|
||||
let mut frame_count = 0;
|
||||
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
let frame_start = Instant::now();
|
||||
|
||||
teleprof::span!("main_frame");
|
||||
|
||||
// Check if paused
|
||||
if teleprof::PAUSE.try_lock().is_err() {
|
||||
println!("Paused!");
|
||||
while teleprof::PAUSE.try_lock().is_err() {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
println!("Resumed!");
|
||||
}
|
||||
|
||||
// Update physics
|
||||
let hit_wall = update_physics(&ball, frame_time.as_secs_f32());
|
||||
|
||||
// If we hit a wall, spawn a thread to pick a new color
|
||||
if hit_wall {
|
||||
let ball_clone = Arc::clone(&ball);
|
||||
thread::spawn(move || {
|
||||
pick_new_color(ball_clone);
|
||||
});
|
||||
}
|
||||
|
||||
// Render
|
||||
render(&ball, &mut framebuffer);
|
||||
|
||||
// Update window
|
||||
window
|
||||
.update_with_buffer(&framebuffer, WIDTH, HEIGHT)
|
||||
.expect("Failed to update window");
|
||||
|
||||
frame_count += 1;
|
||||
if frame_count % 30 == 0 {
|
||||
print_status(&ball, frame_count);
|
||||
}
|
||||
|
||||
// Sleep to maintain 30fps
|
||||
let elapsed = frame_start.elapsed();
|
||||
if elapsed < frame_time {
|
||||
thread::sleep(frame_time - elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_physics(ball: &Arc<Mutex<Ball>>, dt: f32) -> bool {
|
||||
teleprof::span!("update_physics");
|
||||
|
||||
let mut ball = ball.lock().unwrap();
|
||||
|
||||
// Update position
|
||||
ball.x += ball.vx * dt;
|
||||
ball.y += ball.vy * dt;
|
||||
|
||||
let mut hit_wall = false;
|
||||
|
||||
// Bounce off walls
|
||||
let radius = BALL_RADIUS as f32;
|
||||
if ball.x - radius < 0.0 || ball.x + radius > WIDTH as f32 {
|
||||
ball.vx = -ball.vx;
|
||||
ball.x = ball.x.clamp(radius, WIDTH as f32 - radius);
|
||||
hit_wall = true;
|
||||
}
|
||||
|
||||
if ball.y - radius < 0.0 || ball.y + radius > HEIGHT as f32 {
|
||||
ball.vy = -ball.vy;
|
||||
ball.y = ball.y.clamp(radius, HEIGHT as f32 - radius);
|
||||
hit_wall = true;
|
||||
}
|
||||
|
||||
// Simulate some physics computation
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
|
||||
hit_wall
|
||||
}
|
||||
|
||||
fn pick_new_color(ball: Arc<Mutex<Ball>>) {
|
||||
teleprof::span!("pick_new_color");
|
||||
|
||||
// Simulate some "expensive" color selection
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let r = rng.gen_range(50..255);
|
||||
let g = rng.gen_range(50..255);
|
||||
let b = rng.gen_range(50..255);
|
||||
|
||||
let color = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
|
||||
|
||||
let mut ball = ball.lock().unwrap();
|
||||
ball.color = color;
|
||||
|
||||
println!(" → New color selected: RGB({}, {}, {})", r, g, b);
|
||||
}
|
||||
|
||||
fn render(ball: &Arc<Mutex<Ball>>, framebuffer: &mut [u32]) {
|
||||
teleprof::span!("render");
|
||||
|
||||
{
|
||||
teleprof::span!("clear_background");
|
||||
// Clear to dark gray
|
||||
framebuffer.fill(0x2A2A2AFF);
|
||||
}
|
||||
|
||||
{
|
||||
teleprof::span!("draw_ball");
|
||||
let ball = ball.lock().unwrap();
|
||||
|
||||
// Draw ball as a filled circle
|
||||
let cx = ball.x as i32;
|
||||
let cy = ball.y as i32;
|
||||
let radius = BALL_RADIUS as i32;
|
||||
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
// Check if point is inside circle
|
||||
if dx * dx + dy * dy <= radius * radius {
|
||||
let x = cx + dx;
|
||||
let y = cy + dy;
|
||||
|
||||
if x >= 0 && x < WIDTH as i32 && y >= 0 && y < HEIGHT as i32 {
|
||||
let idx = y as usize * WIDTH + x as usize;
|
||||
framebuffer[idx] = ball.color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
teleprof::span!("submit_frame");
|
||||
// Simulate GPU submission
|
||||
thread::sleep(Duration::from_millis(2));
|
||||
}
|
||||
}
|
||||
|
||||
fn print_status(ball: &Arc<Mutex<Ball>>, frame: u32) {
|
||||
teleprof::span!("print_status");
|
||||
|
||||
let ball = ball.lock().unwrap();
|
||||
println!(
|
||||
"Frame {}: Ball at ({:.1}, {:.1})",
|
||||
frame, ball.x, ball.y
|
||||
);
|
||||
}
|
||||
83
examples/demo.rs
Normal file
83
examples/demo.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
// Start the telemetry window
|
||||
teleprof::start();
|
||||
|
||||
println!("Teleprof demo running...");
|
||||
println!("Press Space in the profiler window to pause/unpause");
|
||||
println!("Press Escape in the profiler window to quit");
|
||||
|
||||
// Simulate some work
|
||||
for i in 0..1000 {
|
||||
frame_work(i);
|
||||
|
||||
// Check if paused
|
||||
if teleprof::PAUSE.try_lock().is_err() {
|
||||
println!("Paused!");
|
||||
while teleprof::PAUSE.try_lock().is_err() {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
println!("Resumed!");
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
fn frame_work(frame: u32) {
|
||||
teleprof::span!("frame_work");
|
||||
|
||||
physics_update();
|
||||
render();
|
||||
|
||||
if frame % 10 == 0 {
|
||||
occasional_task();
|
||||
}
|
||||
}
|
||||
|
||||
fn physics_update() {
|
||||
teleprof::span!("physics_update");
|
||||
|
||||
// Spawn some worker threads
|
||||
let handles: Vec<_> = (0..3).map(|i| {
|
||||
thread::spawn(move || {
|
||||
physics_worker(i);
|
||||
})
|
||||
}).collect();
|
||||
|
||||
for handle in handles {
|
||||
handle.join().ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn physics_worker(id: u32) {
|
||||
teleprof::span!("physics_worker");
|
||||
|
||||
// Simulate work
|
||||
let work_ms = 5 + (id * 2);
|
||||
thread::sleep(Duration::from_millis(work_ms as u64));
|
||||
}
|
||||
|
||||
fn render() {
|
||||
teleprof::span!("render");
|
||||
|
||||
build_command_buffer();
|
||||
submit_to_gpu();
|
||||
}
|
||||
|
||||
fn build_command_buffer() {
|
||||
teleprof::span!("build_command_buffer");
|
||||
thread::sleep(Duration::from_millis(3));
|
||||
}
|
||||
|
||||
fn submit_to_gpu() {
|
||||
teleprof::span!("submit_to_gpu");
|
||||
thread::sleep(Duration::from_millis(2));
|
||||
}
|
||||
|
||||
fn occasional_task() {
|
||||
teleprof::span!("occasional_task");
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
28
shell.nix
Normal file
28
shell.nix
Normal file
@@ -0,0 +1,28 @@
|
||||
with import <nixpkgs> { };
|
||||
|
||||
let
|
||||
buildInputs = [
|
||||
# For minifb
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXrandr
|
||||
xorg.libXi
|
||||
|
||||
# Wayland support
|
||||
wayland
|
||||
libxkbcommon
|
||||
];
|
||||
in
|
||||
mkShell {
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
];
|
||||
|
||||
inherit buildInputs;
|
||||
|
||||
shellHook = ''
|
||||
export LD_LIBRARY_PATH="${lib.makeLibraryPath buildInputs}:$LD_LIBRARY_PATH"
|
||||
echo "Teleprof dev environment loaded"
|
||||
echo "Run: cargo run --example demo"
|
||||
'';
|
||||
}
|
||||
458
src/lib.rs
Normal file
458
src/lib.rs
Normal file
@@ -0,0 +1,458 @@
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::Cell;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
// ============================================================================
|
||||
// Public API
|
||||
// ============================================================================
|
||||
|
||||
/// Global PAUSE lock - acquire this to pause the application
|
||||
pub static PAUSE: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(())));
|
||||
|
||||
/// Start the telemetry window in a separate thread
|
||||
pub fn start() {
|
||||
std::thread::spawn(|| {
|
||||
if let Err(e) = window::run() {
|
||||
eprintln!("Teleprof window error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a profiling span - use via the `span!` macro
|
||||
pub struct SpanGuard {
|
||||
span_id: u64,
|
||||
}
|
||||
|
||||
impl SpanGuard {
|
||||
pub fn new(name: &'static str) -> Self {
|
||||
let span_id = next_span_id();
|
||||
let thread_id = std::thread::current().id();
|
||||
let parent_id = PARENT_SPAN.with(|p| p.get());
|
||||
|
||||
// Set ourselves as the current parent for nested spans
|
||||
PARENT_SPAN.with(|p| p.set(Some(span_id)));
|
||||
|
||||
EVENT_SENDER.send(Event::SpanStart {
|
||||
span_id,
|
||||
parent_id,
|
||||
thread_id: thread_id_to_u64(thread_id),
|
||||
name,
|
||||
timestamp: Instant::now(),
|
||||
}).ok();
|
||||
|
||||
Self { span_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SpanGuard {
|
||||
fn drop(&mut self) {
|
||||
EVENT_SENDER.send(Event::SpanEnd {
|
||||
span_id: self.span_id,
|
||||
timestamp: Instant::now(),
|
||||
}).ok();
|
||||
|
||||
// Pop back to parent
|
||||
PARENT_SPAN.with(|p| {
|
||||
// Find parent by looking at active spans (simplified - just clear for now)
|
||||
p.set(None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro for creating a span
|
||||
#[macro_export]
|
||||
macro_rules! span {
|
||||
($name:expr) => {
|
||||
let _span_guard = $crate::SpanGuard::new($name);
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal types and state
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Event {
|
||||
SpanStart {
|
||||
span_id: u64,
|
||||
parent_id: Option<u64>,
|
||||
thread_id: u64,
|
||||
name: &'static str,
|
||||
timestamp: Instant,
|
||||
},
|
||||
SpanEnd {
|
||||
span_id: u64,
|
||||
timestamp: Instant,
|
||||
},
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static PARENT_SPAN: Cell<Option<u64>> = Cell::new(None);
|
||||
}
|
||||
|
||||
static EVENT_SENDER: Lazy<Sender<Event>> = Lazy::new(|| {
|
||||
let (tx, rx) = unbounded();
|
||||
*EVENT_RECEIVER.lock().unwrap() = Some(rx);
|
||||
tx
|
||||
});
|
||||
|
||||
static EVENT_RECEIVER: Lazy<Arc<Mutex<Option<Receiver<Event>>>>> =
|
||||
Lazy::new(|| Arc::new(Mutex::new(None)));
|
||||
|
||||
static SPAN_ID_COUNTER: Lazy<Arc<Mutex<u64>>> = Lazy::new(|| Arc::new(Mutex::new(0)));
|
||||
|
||||
fn next_span_id() -> u64 {
|
||||
let mut counter = SPAN_ID_COUNTER.lock().unwrap();
|
||||
let id = *counter;
|
||||
*counter += 1;
|
||||
id
|
||||
}
|
||||
|
||||
fn thread_id_to_u64(id: std::thread::ThreadId) -> u64 {
|
||||
// Hack: ThreadId doesn't expose inner value, so we format and parse
|
||||
let s = format!("{:?}", id);
|
||||
s.trim_start_matches("ThreadId(")
|
||||
.trim_end_matches(")")
|
||||
.parse()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Window rendering
|
||||
// ============================================================================
|
||||
|
||||
mod window {
|
||||
use super::*;
|
||||
use minifb::{Key, Window, WindowOptions};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const INITIAL_WIDTH: usize = 1280;
|
||||
const INITIAL_HEIGHT: usize = 720;
|
||||
const MAX_EVENTS: usize = 1_000_000; // ~16MB at 16 bytes per event
|
||||
|
||||
// Monokai palette
|
||||
const COLORS: [u32; 8] = [
|
||||
0xF92672, // Pink
|
||||
0xA6E22E, // Green
|
||||
0xFD971F, // Orange
|
||||
0x66D9EF, // Cyan
|
||||
0xAE81FF, // Purple
|
||||
0xE6DB74, // Yellow
|
||||
0xF8F8F2, // White
|
||||
0x75715E, // Gray
|
||||
];
|
||||
|
||||
const BG_COLOR: u32 = 0x272822;
|
||||
const GRID_COLOR: u32 = 0x3E3D32;
|
||||
|
||||
struct CompletedSpan {
|
||||
span_id: u64,
|
||||
parent_id: Option<u64>,
|
||||
thread_id: u64,
|
||||
name: &'static str,
|
||||
start: Instant,
|
||||
end: Instant,
|
||||
}
|
||||
|
||||
struct RingBuffer {
|
||||
spans: Vec<CompletedSpan>,
|
||||
head: usize,
|
||||
pending_starts: HashMap<u64, (u64, Option<u64>, u64, &'static str, Instant)>,
|
||||
}
|
||||
|
||||
impl RingBuffer {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
spans: Vec::with_capacity(MAX_EVENTS),
|
||||
head: 0,
|
||||
pending_starts: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_event(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::SpanStart { span_id, parent_id, thread_id, name, timestamp } => {
|
||||
self.pending_starts.insert(span_id, (span_id, parent_id, thread_id, name, timestamp));
|
||||
}
|
||||
Event::SpanEnd { span_id, timestamp } => {
|
||||
if let Some((span_id, parent_id, thread_id, name, start)) = self.pending_starts.remove(&span_id) {
|
||||
let span = CompletedSpan {
|
||||
span_id,
|
||||
parent_id,
|
||||
thread_id,
|
||||
name,
|
||||
start,
|
||||
end: timestamp,
|
||||
};
|
||||
|
||||
if self.spans.len() < MAX_EVENTS {
|
||||
self.spans.push(span);
|
||||
} else {
|
||||
self.spans[self.head] = span;
|
||||
self.head = (self.head + 1) % MAX_EVENTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn iter(&self) -> Box<dyn Iterator<Item = &CompletedSpan> + '_> {
|
||||
if self.spans.len() < MAX_EVENTS {
|
||||
Box::new(self.spans.iter())
|
||||
} else {
|
||||
// Return items in chronological order from ringbuffer
|
||||
Box::new(self.spans[self.head..].iter().chain(self.spans[..self.head].iter()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ViewState {
|
||||
time_offset: f64, // seconds
|
||||
time_scale: f64, // pixels per second
|
||||
paused: bool,
|
||||
pause_guard: Option<std::sync::MutexGuard<'static, ()>>,
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut window = Window::new(
|
||||
"Teleprof",
|
||||
INITIAL_WIDTH,
|
||||
INITIAL_HEIGHT,
|
||||
WindowOptions::default(),
|
||||
)?;
|
||||
|
||||
window.set_target_fps(60);
|
||||
|
||||
let receiver = EVENT_RECEIVER.lock().unwrap().take()
|
||||
.ok_or("Event receiver not initialized")?;
|
||||
|
||||
let mut buffer = RingBuffer::new();
|
||||
let mut view = ViewState {
|
||||
time_offset: 0.0,
|
||||
time_scale: 100.0, // 100 pixels per second
|
||||
paused: false,
|
||||
pause_guard: None,
|
||||
};
|
||||
|
||||
let mut framebuffer = vec![BG_COLOR; INITIAL_WIDTH * INITIAL_HEIGHT];
|
||||
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
// Drain events from channel
|
||||
while let Ok(event) = receiver.try_recv() {
|
||||
buffer.push_event(event);
|
||||
}
|
||||
|
||||
// Handle input
|
||||
if window.is_key_pressed(Key::Space, minifb::KeyRepeat::No) {
|
||||
view.paused = !view.paused;
|
||||
if view.paused {
|
||||
view.pause_guard = PAUSE.try_lock().ok();
|
||||
} else {
|
||||
view.pause_guard = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current window size
|
||||
let (width, height) = window.get_size();
|
||||
if framebuffer.len() != width * height {
|
||||
framebuffer.resize(width * height, BG_COLOR);
|
||||
}
|
||||
|
||||
// Clear framebuffer
|
||||
framebuffer.fill(BG_COLOR);
|
||||
|
||||
// Render
|
||||
render_frame(&mut framebuffer, width, height, &buffer, &view);
|
||||
|
||||
window.update_with_buffer(&framebuffer, width, height)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_frame(
|
||||
framebuffer: &mut [u32],
|
||||
width: usize,
|
||||
height: usize,
|
||||
buffer: &RingBuffer,
|
||||
view: &ViewState,
|
||||
) {
|
||||
let icicle_height = height / 2;
|
||||
let timeline_height = height - icicle_height;
|
||||
|
||||
// Find time range
|
||||
let spans: Vec<_> = buffer.iter().collect();
|
||||
if spans.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let earliest = spans.iter().map(|s| s.start).min().unwrap();
|
||||
let latest = spans.iter().map(|s| s.end).max().unwrap();
|
||||
let duration = (latest - earliest).as_secs_f64();
|
||||
|
||||
// Draw icicle graph (top half)
|
||||
render_icicle(framebuffer, width, icicle_height, &spans, earliest, duration, view);
|
||||
|
||||
// Draw timeline (bottom half)
|
||||
render_timeline(
|
||||
framebuffer,
|
||||
width,
|
||||
height,
|
||||
icicle_height,
|
||||
timeline_height,
|
||||
&spans,
|
||||
earliest,
|
||||
duration,
|
||||
view,
|
||||
);
|
||||
}
|
||||
|
||||
fn render_icicle(
|
||||
framebuffer: &mut [u32],
|
||||
width: usize,
|
||||
height: usize,
|
||||
spans: &[&CompletedSpan],
|
||||
earliest: Instant,
|
||||
duration: f64,
|
||||
view: &ViewState,
|
||||
) {
|
||||
// Build tree structure
|
||||
let mut roots = Vec::new();
|
||||
let mut children: HashMap<u64, Vec<&CompletedSpan>> = HashMap::new();
|
||||
|
||||
for span in spans {
|
||||
if let Some(parent) = span.parent_id {
|
||||
children.entry(parent).or_default().push(span);
|
||||
} else {
|
||||
roots.push(*span);
|
||||
}
|
||||
}
|
||||
|
||||
// Render each root and its children recursively
|
||||
let y_start = 0;
|
||||
let row_height = 20;
|
||||
|
||||
for root in roots {
|
||||
render_icicle_span(
|
||||
framebuffer,
|
||||
width,
|
||||
height,
|
||||
root,
|
||||
&children,
|
||||
earliest,
|
||||
duration,
|
||||
y_start,
|
||||
row_height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_icicle_span(
|
||||
framebuffer: &mut [u32],
|
||||
width: usize,
|
||||
height: usize,
|
||||
span: &CompletedSpan,
|
||||
children: &HashMap<u64, Vec<&CompletedSpan>>,
|
||||
earliest: Instant,
|
||||
duration: f64,
|
||||
y: usize,
|
||||
row_height: usize,
|
||||
) {
|
||||
if y + row_height > height {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_time = (span.start - earliest).as_secs_f64();
|
||||
let end_time = (span.end - earliest).as_secs_f64();
|
||||
|
||||
let x1 = ((start_time / duration) * width as f64) as usize;
|
||||
let x2 = ((end_time / duration) * width as f64) as usize;
|
||||
|
||||
let color = get_color_for_name(span.name);
|
||||
|
||||
fill_rect(framebuffer, width, x1, y, x2 - x1, row_height, color);
|
||||
|
||||
// Render children
|
||||
if let Some(child_spans) = children.get(&span.span_id) {
|
||||
let child_y = y + row_height;
|
||||
for child in child_spans {
|
||||
render_icicle_span(
|
||||
framebuffer,
|
||||
width,
|
||||
height,
|
||||
child,
|
||||
children,
|
||||
earliest,
|
||||
duration,
|
||||
child_y,
|
||||
row_height,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_timeline(
|
||||
framebuffer: &mut [u32],
|
||||
width: usize,
|
||||
_height: usize,
|
||||
y_offset: usize,
|
||||
timeline_height: usize,
|
||||
spans: &[&CompletedSpan],
|
||||
earliest: Instant,
|
||||
duration: f64,
|
||||
_view: &ViewState,
|
||||
) {
|
||||
// Group by thread
|
||||
let mut threads: HashMap<u64, Vec<&CompletedSpan>> = HashMap::new();
|
||||
for span in spans {
|
||||
threads.entry(span.thread_id).or_default().push(*span);
|
||||
}
|
||||
|
||||
let thread_ids: Vec<_> = threads.keys().copied().collect();
|
||||
let num_threads = thread_ids.len();
|
||||
if num_threads == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let row_height = timeline_height / num_threads.max(1);
|
||||
|
||||
for (i, thread_id) in thread_ids.iter().enumerate() {
|
||||
let y = y_offset + i * row_height;
|
||||
let thread_spans = &threads[thread_id];
|
||||
|
||||
for span in thread_spans {
|
||||
let start_time = (span.start - earliest).as_secs_f64();
|
||||
let end_time = (span.end - earliest).as_secs_f64();
|
||||
|
||||
let x1 = ((start_time / duration) * width as f64) as usize;
|
||||
let x2 = ((end_time / duration) * width as f64).max(x1 as f64 + 1.0) as usize;
|
||||
|
||||
let color = get_color_for_name(span.name);
|
||||
|
||||
fill_rect(framebuffer, width, x1, y, x2 - x1, row_height - 2, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_rect(framebuffer: &mut [u32], width: usize, x: usize, y: usize, w: usize, h: usize, color: u32) {
|
||||
for dy in 0..h {
|
||||
let row = y + dy;
|
||||
for dx in 0..w {
|
||||
let col = x + dx;
|
||||
if col < width {
|
||||
let idx = row * width + col;
|
||||
if idx < framebuffer.len() {
|
||||
framebuffer[idx] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_color_for_name(name: &str) -> u32 {
|
||||
let hash = name.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
|
||||
COLORS[(hash as usize) % COLORS.len()]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user