diff --git a/JKMP.Plugin.Multiplayer/MultiplayerPlugin.cs b/JKMP.Plugin.Multiplayer/MultiplayerPlugin.cs
index 12ce0e4..a79f7f1 100644
--- a/JKMP.Plugin.Multiplayer/MultiplayerPlugin.cs
+++ b/JKMP.Plugin.Multiplayer/MultiplayerPlugin.cs
@@ -16,6 +16,7 @@
using JKMP.Plugin.Multiplayer.Game.Events;
using JKMP.Plugin.Multiplayer.Game.UI;
using JKMP.Plugin.Multiplayer.Matchmaking;
+using JKMP.Plugin.Multiplayer.Native;
using JKMP.Plugin.Multiplayer.Native.Audio;
using JKMP.Plugin.Multiplayer.Networking;
using JKMP.Plugin.Multiplayer.Steam;
@@ -48,6 +49,7 @@ public class MultiplayerPlugin : Core.Plugins.Plugin
public MultiplayerPlugin()
{
Instance = this;
+ Logging.Initialize();
}
public override void OnLoaded()
diff --git a/native/Multiplayer.Native/Bindings.cs b/native/Multiplayer.Native/Bindings.cs
index 0cd7f43..47eb20a 100644
--- a/native/Multiplayer.Native/Bindings.cs
+++ b/native/Multiplayer.Native/Bindings.cs
@@ -232,6 +232,10 @@ public static int opus_context_decompress(IntPtr context, byte[] data, short[] o
}
}
+ [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "initialize_logging")]
+ public static extern bool initialize_logging(OnLogCallback on_log);
+
+
}
public enum CaptureError
@@ -240,6 +244,16 @@ public enum CaptureError
BackendSpecific = 1,
}
+ public enum LogLevel
+ {
+ Verbose = 0,
+ Debug = 1,
+ Info = 2,
+ Warning = 3,
+ Error = 4,
+ Fatal = 5,
+ }
+
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public partial struct DeviceConfig
@@ -734,6 +748,9 @@ IEnumerator IEnumerable.GetEnumerator()
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void OnCapturedDataCallback(Slicei16 x0, float x1);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void OnLogCallback(LogLevel x0, Sliceu8 x1);
+
public partial class AudioContext : IDisposable
{
diff --git a/native/Multiplayer.Native/Logging.cs b/native/Multiplayer.Native/Logging.cs
new file mode 100644
index 0000000..f2ff515
--- /dev/null
+++ b/native/Multiplayer.Native/Logging.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Text;
+using Serilog;
+using Serilog.Events;
+
+namespace JKMP.Plugin.Multiplayer.Native
+{
+ public static class Logging
+ {
+ private static readonly OnLogCallback OnLogCallback;
+
+ static Logging()
+ {
+ // We need a reference to the callback to be able to call it from native code
+ // If passed directly to the native function, it will be garbage collected
+ OnLogCallback = OnLog;
+ }
+
+ public static void Initialize()
+ {
+ Bindings.initialize_logging(OnLogCallback);
+ }
+
+ private static void OnLog(LogLevel logLevel, Sliceu8 utf8Message)
+ {
+ string message;
+
+ unsafe
+ {
+ fixed (byte* messagePtr = &utf8Message.ReadOnlySpan.GetPinnableReference())
+ {
+ message = Encoding.UTF8.GetString(messagePtr, utf8Message.Count);
+ }
+ }
+
+ var logEventLevel = (LogEventLevel)logLevel;
+ NativeLog.Logger.Write(logEventLevel, (Exception?)null, "{nativeMessage}", message);
+ }
+ }
+
+ internal static class NativeLog
+ {
+ public static readonly ILogger Logger = Log.Logger.ForContext(typeof(NativeLog));
+ }
+}
\ No newline at end of file
diff --git a/native/Multiplayer.Native/Multiplayer.Native.csproj b/native/Multiplayer.Native/Multiplayer.Native.csproj
index dab1dc7..a86e820 100644
--- a/native/Multiplayer.Native/Multiplayer.Native.csproj
+++ b/native/Multiplayer.Native/Multiplayer.Native.csproj
@@ -16,6 +16,7 @@
+
diff --git a/native/Multiplayer.Native/multiplayer_native.dll b/native/Multiplayer.Native/multiplayer_native.dll
index ff087e7..2699d8f 100644
Binary files a/native/Multiplayer.Native/multiplayer_native.dll and b/native/Multiplayer.Native/multiplayer_native.dll differ
diff --git a/native/multiplayer_native/Cargo.toml b/native/multiplayer_native/Cargo.toml
index 70e8b51..0d53244 100644
--- a/native/multiplayer_native/Cargo.toml
+++ b/native/multiplayer_native/Cargo.toml
@@ -15,4 +15,5 @@ cpal = "0.13.5"
audiopus = "0.3.0-rc.0"
anyhow = "1.0.53"
samplerate = "0.2.4"
-ringbuffer = "0.8.2"
\ No newline at end of file
+ringbuffer = "0.8.2"
+log = { version = "0.4.14", features = ["std"] }
\ No newline at end of file
diff --git a/native/multiplayer_native/src/audio_capture/capture_context.rs b/native/multiplayer_native/src/audio_capture/capture_context.rs
index c36ef18..8c286c2 100644
--- a/native/multiplayer_native/src/audio_capture/capture_context.rs
+++ b/native/multiplayer_native/src/audio_capture/capture_context.rs
@@ -353,7 +353,7 @@ impl AudioContext {
self.active_device = device;
- println!("Active device set to {:?}, {:?}", device_name, config);
+ log::trace!("Active device set to {:?}, {:?}", device_name, config);
Ok(())
}
diff --git a/native/multiplayer_native/src/audio_capture/opus_context.rs b/native/multiplayer_native/src/audio_capture/opus_context.rs
index d4524c4..7882a96 100644
--- a/native/multiplayer_native/src/audio_capture/opus_context.rs
+++ b/native/multiplayer_native/src/audio_capture/opus_context.rs
@@ -44,7 +44,7 @@ impl OpusContext {
return len as i32;
}
Err(err) => {
- println!("Failed to encode: {:?}", err);
+ log::warn!("Failed to encode: {:?}", err);
}
}
@@ -79,7 +79,7 @@ impl OpusContext {
return len as i32;
}
Err(err) => {
- println!("Failed to decode: {:?}", err);
+ log::warn!("Failed to decode: {:?}", err);
}
}
diff --git a/native/multiplayer_native/src/lib.rs b/native/multiplayer_native/src/lib.rs
index 4fc1969..eb7a941 100644
--- a/native/multiplayer_native/src/lib.rs
+++ b/native/multiplayer_native/src/lib.rs
@@ -4,10 +4,11 @@ use audio_capture::capture_context::AudioContext;
use audio_capture::opus_context::OpusContext;
use interoptopus::{
- ffi_type, pattern, patterns::result::FFIError, Error, Inventory, InventoryBuilder,
+ ffi_type, function, pattern, patterns::result::FFIError, Error, Inventory, InventoryBuilder,
};
pub mod audio_capture;
+pub mod logging;
#[ffi_type(patterns(ffi_error))]
#[repr(C)]
@@ -78,5 +79,6 @@ pub fn ffi_inventory() -> Inventory {
InventoryBuilder::new()
.register(pattern!(AudioContext))
.register(pattern!(OpusContext))
+ .register(function!(logging::initialize_logging))
.inventory()
}
diff --git a/native/multiplayer_native/src/logging.rs b/native/multiplayer_native/src/logging.rs
new file mode 100644
index 0000000..106a228
--- /dev/null
+++ b/native/multiplayer_native/src/logging.rs
@@ -0,0 +1,55 @@
+use interoptopus::patterns::slice::FFISlice;
+use interoptopus::{callback, ffi_function, ffi_type};
+use log::{LevelFilter, Metadata, Record};
+
+// Note that this enum needs to match serilog's enum, otherwise casting will not work correctly
+#[repr(C)]
+#[ffi_type]
+pub enum LogLevel {
+ Verbose,
+ Debug,
+ Info,
+ Warning,
+ Error,
+ Fatal,
+}
+
+callback!(OnLogCallback(log_level: LogLevel, message: FFISlice));
+
+#[ffi_function]
+#[no_mangle]
+pub extern "C" fn initialize_logging(on_log: OnLogCallback) -> bool {
+ log::set_boxed_logger(Box::new(CallbackLogger {
+ on_log_callback: on_log,
+ }))
+ .map(|()| log::set_max_level(LevelFilter::Trace))
+ .is_ok()
+}
+
+struct CallbackLogger {
+ on_log_callback: OnLogCallback,
+}
+
+impl log::Log for CallbackLogger {
+ fn enabled(&self, _: &Metadata) -> bool {
+ true
+ }
+
+ fn log(&self, record: &Record) {
+ let log_level = match record.level() {
+ log::Level::Error => LogLevel::Error,
+ log::Level::Warn => LogLevel::Warning,
+ log::Level::Info => LogLevel::Info,
+ log::Level::Debug => LogLevel::Debug,
+ log::Level::Trace => LogLevel::Verbose,
+ };
+ let mut message = String::new();
+
+ message.push_str(format!("{}", record.args()).as_str());
+
+ self.on_log_callback
+ .call(log_level, message.as_bytes().into());
+ }
+
+ fn flush(&self) {}
+}