Main menu for a DSS-inspired streaming media application, written in Rust.
To build this project on Windows, macOS, or Linux, you will need a recent version of the Rust toolchain at least version 1.46.0, at minimum, but versions 1.48.0 and newer are strongly recommended because they perform better with async code (rust-lang/rust#78410).
If rustup
is available on your system, the included
rust-toolchain
file at the root directory of this
repository should automatically fetch and install the Rust toolchain for you, if
not already present on your system, as soon as you execute any cargo
command
in the shell.
The following external dependencies will also be required on your system:
- SDL2, for system window and render context management, input handling, and hardware-accelerated rendering.
- SDL2_image, for loading arbitrary image files as SDL textures.
- SDL2_ttf, for loading and rendering TrueType fonts as SDL textures.
vcpkg.exe install sdl2:x64-windows sdl2-image:x64-windows sdl2-ttf:x64-windows
brew install sdl2 sdl2_image sdl2_ttf
# Ubuntu/Debian
sudo apt install libsdl2 libsdl2-image libsdl2-ttf
# Arch Linux
sudo pacman -Sy sdl2 sdl2_image sdl2_ttf
To compile the application in release mode and start it, simply run this command in your terminal:
cargo run --release
To execute the included unit test suite, run:
cargo test
To generate HTML documentation for the public crate API, run:
cargo doc --open
The input controls for navigating the UI are listed below:
Action | Controls |
---|---|
Navigate menu | ↑, ↓, ←, → |
Toggle fullscreen | F11 |
Close window | Esc or "close" button |
Like many idiomatic Rust projects, this service is split into a binary crate
(the main.rs
file) and a library crate (lib.rs
and the rest). This is to
facilitate simpler unit and integration testing under Cargo, should we require
more of it in the future.
The main.rs
is a very thin shim over dss_menu::app::App
, which manages the
main loop, UI rendering, and drives the dss_menu::menu::Menu
business logic
forward.
There is also a single background I/O thread for fetching arbitrary files over
HTTP and caching them in the OS temp directory, which is spawned on app startup;
the implementation for this is located in src/fetcher.rs
. This thread can
process many async HTTP downloads concurrently without resorting to spawning one
thread per connection, and sends completed files back to the main thread as they
become available.
The JSON schema serde
types are located in src/schema.rs
and its submodules.
-
This application will run on an OS with a system allocator available.
-
This class of application is optimized for mobile devices or smart TVs, and as such should try to eliminate heap allocation and dynamic dispatch where possible, and also put the CPU to sleep when applicable to consume less power.
-
This application will fetch and stream resources from the Internet as they become available while avoiding blocking the main loop, and may require some form of async concurrency in order to scale efficiently without spawning more than two OS threads, one for the application and another for network I/O.
-
Cached files will not persist in between individual runs of the application.
-
Dynamically populate ref sets as they scroll into view (didn't quite have time to build a widget or callback for that). We are capable of parsing this data, though, since the schema types exist.
-
Draw rectangular cursor of selected menu tile with rounded corners.
-
Implement animations by passing the delta time between frames to
Widget::update()
and using linear interpolation inMenu::select_tile()
to gradually select and deselect tiles. -
Load the video art MP4 files for use as background animations for the currently selected tile.
-
Skip the SDL hardware-accelerated rendering context and offload even more manual rendering computations from the CPU to the GPU.
-
Cache individually rendered TTF glyphs as SDL textures to make repeated text rendering faster, a la grimfang4/SDL_FontCache.
-
Find (or write) an alternative async executor which allows for explicit handling of out-of-memory errors.
-
Try to reduce heap allocations by using
serde
in zero-copy deserialization mode and adding a lifetime to thehome.json
AST (thereby using&str
overString
when possible). -
Perhaps the number of
String
andUrl
copies made during file fetching and polling could be reduced by sending references instead of values over the channel, but this would be really tedious to implement due to lifetimes, to say the least. -
Add text logging and/or profiling with Criterion.rs and
cargo bench
. -
Change
App
to contain a stack ofState
s, rather than just one, and implement a pushdown automaton with switch/push/pop state transitions to create modal interfaces and other screens.
-
anyhow
is used to eliminate boilerplate from error handling. This commonly used crate eliminates the need to create your own error struct or enum for application-type Rust projects (as opposed to library-type projects, wherethiserror
is more popular) and reduces the number of manual implementations ofDebug
,Display
,From
, andstd::error::Error
as well as conversions between them. -
flume
is a MPMC channel library that fixes numerous bugs and deficiencies seen instd::sync::mpsc
, and most notably works with mixed sync/async code. This library is used for passing data between the synchronous rendering thread and the asynchronous background I/O thread without blocking the main loop. -
fnv
provides a faster and cheaper hashing algorithm than the Rust standard library, which is intended to be DoS resistant by default. We don't need this protection for local rendering uses, though, andfnv
performs way better during hot loops where map accesses and inserts are frequent. -
futures-util
contains common async traits not yet available in the Rust standard library (e.g.FutureExt
,StreamExt
, and so on) as well as theAbortable
combinator andpoll_fn()
function used in tests. -
sdl2
provides native Rust bindings to SDL 2.0, as well asSDL_image
andSDL_ttf
for image loading and font rendering, respectively. -
serde
andserde_json
provide JSON deserialization with pretty good performance for consuming the DSShome.json
API. -
reqwest
is an async HTTP client used for fetching JSON and image files from the DSS API while the main thread is busy rendering. It happens to includerustls
as an optional statically-linked pure Rust alternative to OpenSSL, which is nice because it eliminates one more runtime dependency and uses less memory. -
tempfile
is used for locating the OS temporary directory and creating temporary files that delete themselves after dropping, a facility not provided by the Rust standard library. -
tokio
is used to spawn multiple async tasks and join them concurrently while using minimal resources. Although Rust provides zero-cost async (in the C++ sense), it does not include an executor in the standard library to drive those futures, requiring users to choose one from Crates.io or write their own instead. This crate is compiled in single-threaded mode to stay lightweight. -
url
is used for parsing URL values from strings, both at deserialization time and for use withreqwest
. -
uuid
is used only for type-safety while deserializing UUIDs from JSON.
Includes the Cocogoose Pro TrueType font family, which is free for personal use.