2
Fork 0

Rework plugin structure
All checks were successful
build Build for the target platform
lint/clippy Check for common mistakes and opportunities for code improvement

Also implements logging through the common `log` macros.
This commit is contained in:
Lucas Schwiderski 2025-05-08 00:52:57 +02:00
parent 1aa84e3618
commit c3f7d54626
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
8 changed files with 171 additions and 52 deletions

5
Cargo.lock generated
View file

@ -69,6 +69,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"libc", "libc",
"log",
] ]
[[package]] [[package]]
@ -110,9 +111,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "memchr" name = "memchr"

View file

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
libc = "0.2.144" libc = "0.2.144"
log = { version = "0.4.27", features = ["release_max_level_info"] }
[build-dependencies] [build-dependencies]
bindgen = "0.71.0" bindgen = "0.71.0"

View file

@ -24,7 +24,7 @@ RUN set -eux; \
# And to keep that to a minimum, we still delete the stuff we don't need. # And to keep that to a minimum, we still delete the stuff we don't need.
rm -rf /root/.xwin-cache; rm -rf /root/.xwin-cache;
FROM rust:slim-bullseye AS final FROM rust:1.86.0-slim-bullseye AS final
ARG LLVM_VERSION=18 ARG LLVM_VERSION=18
ENV KEYRINGS=/usr/local/share/keyrings ENV KEYRINGS=/usr/local/share/keyrings
@ -37,6 +37,7 @@ RUN set -eux; \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
gpg \ gpg \
make \
; \ ; \
mkdir -p $KEYRINGS; \ mkdir -p $KEYRINGS; \
gpg --dearmor > $KEYRINGS/llvm.gpg < /root/llvm-snapshot.gpg.key; \ gpg --dearmor > $KEYRINGS/llvm.gpg < /root/llvm-snapshot.gpg.key; \
@ -69,7 +70,6 @@ RUN set -eux; \
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100; \ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100; \
update-alternatives --install /usr/bin/ld ld /usr/bin/ld.lld 100; \ update-alternatives --install /usr/bin/ld ld /usr/bin/ld.lld 100; \
rustup target add x86_64-pc-windows-msvc; \ rustup target add x86_64-pc-windows-msvc; \
rustup default stable-x86_64-pc-windows-msvc; \
rustup component add rust-src; \ rustup component add rust-src; \
rustup update; \ rustup update; \
apt-get remove -y --auto-remove \ apt-get remove -y --auto-remove \

View file

@ -1,16 +1,103 @@
set dotenv-load
set dotenv-filename := '.envrc'
set dotenv-required
set allow-duplicate-variables
fly_target := "main" fly_target := "main"
image_name := "dt-plugin-builder"
steam_library := env("steam_library")
game_dir := steam_library / "steamapps" / "common" / "Warhammer 40,000 DARKTIDE"
log_dir := env("APPDATA") / "Fatshark" / "Darktide" / "console_logs"
game_dir := if path_exists(game_dir) == true {
game_dir
} else {
error("Game directory does not exist, please set correct library path: " + game_dir)
}
proton_dir := home_directory() / ".steam" / "steam"
export STEAM_COMPAT_DATA_PATH := steam_library / "steamapps" / "compatdata" / "1361210"
export SteamAppId := "1361210"
release := env("RELEASE", "false")
image: image:
docker build -t dt-plugin-builder . docker build -t {{image_name}} .
build: # Compile the Windows DLL through a Docker image
[unix]
compile:
docker run \ docker run \
--rm \ --rm \
-t \ -t \
--user "$(id -u):$(id -g)" \ --user "$(id -u):$(id -g)" \
-v $HOME/.cargo/registry:/usr/local/cargo/registry \
-v $HOME/.cargo/git:/usr/local/cargo/git \
-v $HOME/.rustup/toolchains:/usr/local/rustup/toolchains \
-v ./:/src/plugin \ -v ./:/src/plugin \
dt-plugin-builder \ {{image_name}} \
cargo build -Zbuild-std --target x86_64-pc-windows-msvc cargo build -Zbuild-std --target x86_64-pc-windows-msvc {{ if release != "false" { "--release" } else { "" } }}
# Compile the DLL and install it to the game's plugin directory
[unix]
build: compile
cp target/x86_64-pc-windows-msvc/{{ if release != "false" { "release" } else { "debug" } }}/dt_p2p.dll "{{game_dir}}/binaries/plugins/dt_p2p_pluginw64_release.dll"
# Open the newest game log file
[unix]
log:
#!/bin/sh
set -e
file="$(\ls "{{log_dir}}" | tail -1)"
echo "$(tput bold)Opening log file $file$(tput sgr0)"
exec bat "{{log_dir}}/$file"
# Run the game through Proton
[unix]
game:
#!/bin/sh
set -e
cd "{{game_dir / "binaries"}}"
proton waitforexitandrun ./Darktide.exe \
-eac-untrusted \
--bundle-dir ../bundle \
--ini settings \
--backend-auth-service-url https://bsp-auth-prod.atoma.cloud \
--backend-title-service-url https://bsp-td-prod.atoma.cloud \
-game \
-launcher_verification_passed_crashify_property false
# Run the game
#
# Untested!
[windows]
game:
#!cmd.exe
cd "{{game_dir / "binaries"}}"
./Darktide.exe \
-eac-untrusted \
--bundle-dir ../bundle \
--ini settings \
--backend-auth-service-url https://bsp-auth-prod.atoma.cloud \
--backend-title-service-url https://bsp-td-prod.atoma.cloud \
-game \
-launcher_verification_passed_crashify_property false
# Compile the plugin through a Docker image
#
# Make sure to run `just image` first, to build the image.
# Untested: I don't know, if this works with Windows' Docker Desktop.
# I also don't know if/where cargo and rustup keep their caches on Windows.
[windows]
compile-docker:
docker run \
--rm \
-t \
-v ./:/src/plugin \
{{image_name}} \
cargo build -Zbuild-std --target x86_64-pc-windows-msvc {{ if release != "false" { "--release" } else { "" } }}
set-base-pipeline: set-base-pipeline:
fly -t {{fly_target}} set-pipeline \ fly -t {{fly_target}} set-pipeline \

View file

@ -1,11 +1,12 @@
use std::ffi::{c_char, CString}; use std::ffi::{c_char, CString};
use std::sync::OnceLock; use std::sync::OnceLock;
mod lua;
mod plugin; mod plugin;
mod stingray_sdk; mod stingray_sdk;
use plugin::Plugin; use plugin::Plugin;
use stingray_sdk::{GetApiFunction, PluginApi, PluginApiID}; use stingray_sdk::{GetApiFunction, LoggingApi, LuaApi, PluginApi, PluginApiID};
// TODO: Change these // TODO: Change these
/// The name that the plugin is registered to the engine as. /// The name that the plugin is registered to the engine as.
@ -15,6 +16,20 @@ pub const PLUGIN_NAME: &str = "dt-plugin-template";
pub const MODULE_NAME: &str = "DTPluginTemplate"; pub const MODULE_NAME: &str = "DTPluginTemplate";
static PLUGIN: OnceLock<Plugin> = OnceLock::new(); static PLUGIN: OnceLock<Plugin> = OnceLock::new();
static LOGGER: OnceLock<LoggingApi> = OnceLock::new();
static LUA: OnceLock<LuaApi> = OnceLock::new();
/// A macro to make accessing global statics a little more convenient.
#[macro_export]
macro_rules! global {
($name:ident) => {{
if cfg!(debug_assertions) {
$name.get().expect("global has not been initialized")
} else {
unsafe { $name.get().unwrap_unchecked() }
}
}};
}
#[no_mangle] #[no_mangle]
pub extern "C" fn get_name() -> *const c_char { pub extern "C" fn get_name() -> *const c_char {
@ -24,26 +39,25 @@ pub extern "C" fn get_name() -> *const c_char {
#[no_mangle] #[no_mangle]
pub extern "C" fn setup_game(get_engine_api: GetApiFunction) { pub extern "C" fn setup_game(get_engine_api: GetApiFunction) {
let plugin = Plugin::new(get_engine_api); let logger = LOGGER.get_or_init(|| LoggingApi::get(get_engine_api));
log::set_logger(logger).expect("Failed to set global logger.");
log::set_max_level(log::LevelFilter::Info);
let _ = LUA.get_or_init(|| LuaApi::get(get_engine_api));
let plugin = PLUGIN.get_or_init(Plugin::new);
plugin.setup_game(); plugin.setup_game();
PLUGIN
.set(plugin)
.expect("Failed to initalize global plugin object.");
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn shutdown_game() { pub extern "C" fn shutdown_game() {
// Safety: The engine ensures that `setup_game` was called before this, so `PLUGIN` has been let plugin = global!(PLUGIN);
// initialized.
let plugin = unsafe { PLUGIN.get().unwrap_unchecked() };
plugin.shutdown_game(); plugin.shutdown_game();
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn update_game(dt: f32) { pub extern "C" fn update_game(dt: f32) {
// Safety: The engine ensures that `setup_game` was called before this, so `PLUGIN` has been let plugin = global!(PLUGIN);
// initialized.
let plugin = unsafe { PLUGIN.get().unwrap_unchecked() };
plugin.update_game(dt); plugin.update_game(dt);
} }

16
src/lua.rs Normal file
View file

@ -0,0 +1,16 @@
use crate::stingray_sdk::lua_State;
use crate::{LUA, global};
pub extern "C" fn do_something(l: *mut lua_State) -> i32 {
let lua = global!(LUA);
if let Some(name) = lua.tolstring(l, 1) {
lua.pushstring(
l,
format!("[do_something] Hello from Rust, {}", name.to_string_lossy()),
);
1
} else {
0
}
}

View file

@ -1,47 +1,23 @@
use crate::stingray_sdk::{lua_State, GetApiFunction, LoggingApi, LuaApi}; use log::info;
use crate::{MODULE_NAME, PLUGIN, PLUGIN_NAME};
pub(crate) struct Plugin { use crate::{LUA, MODULE_NAME, PLUGIN_NAME, global, lua};
pub log: LoggingApi,
pub lua: LuaApi,
}
extern "C" fn do_something(l: *mut lua_State) -> i32 { pub(crate) struct Plugin {}
// Safety: Plugin must have been initialized for this to be registered as module
// function.
let plugin = unsafe { PLUGIN.get().unwrap_unchecked() };
if let Some(name) = plugin.lua.tolstring(l, 1) {
plugin.lua.pushstring(
l,
format!("[do_something] Hello from Rust, {}", name.to_string_lossy()),
);
1
} else {
0
}
}
impl Plugin { impl Plugin {
pub fn new(get_engine_api: GetApiFunction) -> Self { pub fn new() -> Self {
let log = LoggingApi::get(get_engine_api); Self {}
let lua = LuaApi::get(get_engine_api);
Self { log, lua }
} }
pub fn setup_game(&self) { pub fn setup_game(&self) {
self.log.info( info!("[setup_game] Hello, world! This is {}!", PLUGIN_NAME);
PLUGIN_NAME,
format!("[setup_game] Hello, world! This is {}!", PLUGIN_NAME),
);
self.lua let lua = global!(LUA);
.add_module_function(MODULE_NAME, "do_something", do_something); lua.add_module_function(MODULE_NAME, "do_something", lua::do_something);
} }
pub fn shutdown_game(&self) { pub fn shutdown_game(&self) {
self.log info!("[shutdown_game] Goodbye, world!");
.info(PLUGIN_NAME, "[shutdown_game] Goodbye, world!");
} }
pub fn update_game(&self, _dt: f32) {} pub fn update_game(&self, _dt: f32) {}

View file

@ -18,6 +18,7 @@ pub use bindings::PluginApi;
pub use bindings::PluginApiID; pub use bindings::PluginApiID;
use bindings::lua_CFunction; use bindings::lua_CFunction;
pub use bindings::lua_State; pub use bindings::lua_State;
use log::Level;
impl std::default::Default for PluginApi { impl std::default::Default for PluginApi {
fn default() -> Self { fn default() -> Self {
@ -107,6 +108,29 @@ impl LoggingApi {
} }
} }
impl log::Log for LoggingApi {
fn enabled(&self, metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let line = if let Some(s) = record.args().as_str() {
format!("[{}] {}", record.level(), s)
} else {
format!("[{}] {}", record.level(), record.args())
};
self.info(record.target(), line);
}
// The engine does not expose a method to flush the log
fn flush(&self) {}
}
#[repr(i32)] #[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LuaType { pub enum LuaType {