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) {} +}