diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 9e98ff5..ea777d8 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -4,11 +4,14 @@
{
"type": "cargo",
"command": "build",
+ "args": [
+ "--release"
+ ],
"problemMatcher": [
"$rustc"
],
"group": "build",
- "label": "rust: cargo build --release"
+ "label": "rust: cargo build"
},
{
"type": "shell",
diff --git a/Cargo.toml b/Cargo.toml
index 9e877bf..0f90ea2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,10 +10,14 @@ crate-type = ["cdylib"]
[dependencies]
vulkan-rs = { git = "https://gavania.de/hodasemi/vulkan_lib.git" }
+assetpath = { git = "https://gavania.de/hodasemi/vulkan_lib.git" }
+utilities = { git = "https://gavania.de/hodasemi/utilities.git" }
rfactor_sm_reader = { git = "https://gavania.de/hodasemi/rfactor_sm_reader.git" }
+ui = { git = "https://gavania.de/hodasemi/ui.git" }
anyhow = { version = "1.0.68", features = ["backtrace"] }
cgmath = { version = "0.18.0", features = ["swizzle", "serde"] }
paste = "1.0.11"
serde = "1.0.152"
serde_json = "1.0.91"
+ringbuf = "0.3.2"
diff --git a/README.md b/README.md
index 7dced95..8d702f0 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,31 @@
This project is an attempt to render custom HUD elements for rFactor2 by being a Vulkan layer.
# Current state
-I just started doing it. That means it isn't very usable right now. I'm working on a radar right now as the first element.
+I would consider the following elements as working:
+
+* Leader Board (enabled on in race)
+* Radar
+* Pedals
# How to enable
-* Build this repository `cargo build --release`
-* Change the path where the `libvk_layer_rs.so` is located (`library_path` parameter in the rFactorOverlay.json file)
-* Put the rFactorOverlay.json into a layer directory ([layer directories](https://vulkan.lunarg.com/doc/view/1.3.216.0/mac/loader_and_layer_interface.html#user-content-linux-layer-discovery))
+
+### Archlinux based
+Simply use the PKGBUILD from the pkgbuild directory ([How to use it](https://wiki.archlinux.org/title/Makepkg))
+
+### Manual installation
+1) Build this repository `cargo build --release`
+2) Change the path where the `libvk_layer_rs.so` is located (`library_path` parameter in the rFactorOverlay.json file)
+3) Put the rFactorOverlay.json into a layer directory ([layer directories](https://vulkan.lunarg.com/doc/view/1.3.216.0/mac/loader_and_layer_interface.html#user-content-linux-layer-discovery))
+
+---
+
* Add `RFACTOR_HUD=1` to steam launch command (example: `RFACTOR_HUD=1 %command%`)
A config file is generated the first time you start it at: `$HOME/.config/rFactorHUD/config.json`
+### Requirement
+You need to have rFactor2 memory plugin shared file enabled ([rF2SharedMemoryMapPlugin_Wine](https://github.com/schlegp/rF2SharedMemoryMapPlugin_Wine))
+
# Resources
## Vulkan Layer
diff --git a/build.rs b/build.rs
index d0fb369..5586f94 100644
--- a/build.rs
+++ b/build.rs
@@ -10,8 +10,10 @@ const VK_HEADER: &[&str] = &[
const FN_PREFIX: &str = "PFN_";
const SHADER: &[&str] = &[
- "src/overlay/shader/single_color.vert",
- "src/overlay/shader/single_color.frag",
+ "src/overlay/elements/radar/single_color.vert",
+ "src/overlay/elements/radar/single_color.frag",
+ "src/overlay/elements/pedals/history.vert",
+ "src/overlay/elements/pedals/history.frag",
];
fn query_vulkan_function_typedefs() {
diff --git a/font.png b/font.png
new file mode 100644
index 0000000..d10f072
Binary files /dev/null and b/font.png differ
diff --git a/src/overlay/elements/gui.xsd b/src/overlay/elements/gui.xsd
new file mode 100644
index 0000000..28c6e1c
--- /dev/null
+++ b/src/overlay/elements/gui.xsd
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/overlay/elements/leaderboard/leaderboard_entry.xml b/src/overlay/elements/leaderboard/leaderboard_entry.xml
new file mode 100644
index 0000000..a61afb4
--- /dev/null
+++ b/src/overlay/elements/leaderboard/leaderboard_entry.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/overlay/elements/leaderboard/leaderboard_grid.xml b/src/overlay/elements/leaderboard/leaderboard_grid.xml
new file mode 100644
index 0000000..0ad24dc
--- /dev/null
+++ b/src/overlay/elements/leaderboard/leaderboard_grid.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/overlay/elements/leaderboard/mod.rs b/src/overlay/elements/leaderboard/mod.rs
new file mode 100644
index 0000000..f189029
--- /dev/null
+++ b/src/overlay/elements/leaderboard/mod.rs
@@ -0,0 +1,291 @@
+use std::{
+ ffi::{c_char, CStr},
+ sync::Arc,
+};
+
+use anyhow::Result;
+use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
+use ui::prelude::*;
+use utilities::prelude::Color;
+
+use crate::overlay::{
+ rfactor_data::{DataReceiver, GamePhase},
+ UiOverlay,
+};
+use crate::write_log;
+
+pub struct LeaderBoard {
+ gui_handler: Arc,
+ gui: Arc,
+ main_grid: Arc,
+
+ entries: Vec,
+
+ entry_backgrounds: [Color; 2],
+ player_background: Color,
+}
+
+impl LeaderBoard {
+ const GRID: &str = include_str!("leaderboard_grid.xml");
+ const ENTRY: &str = include_str!("leaderboard_entry.xml");
+
+ pub fn new(gui_handler: &Arc) -> Result {
+ let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?;
+
+ let main_grid = gui.element("main_grid")?;
+
+ Ok(Self {
+ gui_handler: gui_handler.clone(),
+ gui,
+ main_grid,
+
+ entries: Vec::new(),
+
+ entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
+ player_background: Color::try_from("#b4bf26")?,
+ })
+ }
+
+ fn c_char_to_string(c: [c_char; 32usize]) -> String {
+ unsafe { CStr::from_ptr(&c as *const c_char) }
+ .to_str()
+ .unwrap()
+ .to_string()
+ }
+
+ fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
+ for vehicle_scoring in vehicle_scorings {
+ let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);
+
+ // check driver list
+ match self
+ .entries
+ .iter_mut()
+ .find(|entry| vehicle_scoring.mID == entry.id())
+ {
+ Some(entry) => {
+ if entry.name() != driver_name {
+ entry.change_name(driver_name)?;
+ }
+
+ entry.update_place(vehicle_scoring.mPlace)?;
+ entry.update_time_behind_leader(vehicle_scoring.mTimeBehindLeader)?;
+ entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
+ }
+ None => {
+ let entry = LeaderBoardEntry::new(
+ &self.gui_handler,
+ vehicle_scoring.mID,
+ driver_name,
+ vehicle_scoring.mPlace,
+ vehicle_scoring.mTimeBehindLeader,
+ vehicle_scoring.mTimeBehindNext,
+ )?;
+
+ self.entries.push(entry);
+ }
+ }
+ }
+
+ write_log!("create entries");
+
+ // check if entry count in grid is the same as the gathered entries
+ let force_update = if !self.entries.is_empty()
+ && self
+ .main_grid
+ .child_at(0, self.entries.len() - 1)?
+ .is_none()
+ {
+ for i in 0..self.entries.len() {
+ self.main_grid.detach(0, i)?;
+ }
+
+ true
+ } else {
+ false
+ };
+
+ // check if any entry needs resorting
+ if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) {
+ write_log!("leader board update required");
+
+ self.entries
+ .sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));
+
+ for (i, entry) in self.entries.iter_mut().enumerate() {
+ entry.resorting_finished();
+ entry.change_background_color(self.entry_backgrounds[i % 2])?;
+
+ self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?;
+ }
+ }
+
+ if self.entries.is_empty() {
+ self.gui.disable()?;
+ } else {
+ self.gui.enable()?;
+ }
+
+ Ok(())
+ }
+}
+
+impl UiOverlay for LeaderBoard {}
+
+impl DataReceiver for LeaderBoard {
+ fn scoring_update(
+ &mut self,
+ phase: GamePhase,
+ vehicle_scorings: &[VehicleScoringInfoV01],
+ ) -> Result<()> {
+ write_log!("=================== leader board: scoring update ===================");
+
+ match phase {
+ GamePhase::TestDay => self.gui.disable()?,
+ GamePhase::Practice => self.gui.disable()?,
+ GamePhase::Qualifying => self.gui.disable()?,
+ GamePhase::Warmup => self.gui.disable()?,
+ GamePhase::Race => {
+ self.race_leaderboard(vehicle_scorings)?;
+ }
+ }
+
+ write_log!("leader board update finished");
+
+ Ok(())
+ }
+
+ fn telemetry_update(
+ &mut self,
+ player_id: Option,
+ _telemetries: &[rF2VehicleTelemetry],
+ ) -> Result<()> {
+ if let Some(player_id) = player_id {
+ if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
+ entry.change_background_color(self.player_background)?;
+ }
+ }
+
+ Ok(())
+ }
+}
+
+struct LeaderBoardEntry {
+ id: i32,
+
+ name: String,
+ place: u8,
+ time_behind_leader: f64,
+ time_behind_next: f64,
+
+ snippet: Arc,
+
+ grid: Arc,
+ name_label: Arc