kitgit

tirbofish/dropbear · commit

a21b036ed943f39459cab1748260e7dfa6075143

feature: got a console working, so the output can be sent through. Unverified

Thribhu K <4tkbytes@pm.me> · 2026-01-24 14:40

view full diff

diff --git a/crates/eucalyptus-core/src/scripting/jni.rs b/crates/eucalyptus-core/src/scripting/jni.rs
index 5d97c3b..c311882 100644
--- a/crates/eucalyptus-core/src/scripting/jni.rs
+++ b/crates/eucalyptus-core/src/scripting/jni.rs
@@ -390,8 +390,23 @@ impl JavaContext {
                 )?
                 .l()?;
 
-            let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?;
-            let log_writer_obj = env.new_object(std_out_writer_class, "()V", &[])?;
+            let log_writer_obj = match RUNTIME_MODE.get() {
+                Some(RuntimeMode::Editor) | Some(RuntimeMode::PlayMode) => {
+                    let port = 56624;
+                    if std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).is_ok() {
+                        let socket_writer_class = env.find_class("com/dropbear/logging/SocketWriter")?;
+                        env.new_object(socket_writer_class, "()V", &[])?
+                    } else {
+                        log::debug!("Editor console not reachable at 127.0.0.1:{}. Falling back to StdoutWriter.", port);
+                        let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?;
+                        env.new_object(std_out_writer_class, "()V", &[])?
+                    }
+                }
+                _ => {
+                    let std_out_writer_class = env.find_class("com/dropbear/logging/StdoutWriter")?;
+                    env.new_object(std_out_writer_class, "()V", &[])?
+                }
+            };
 
             if self.system_manager_instance.is_none() {
                 let engine_ref = self
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index a0e0ad7..83200aa 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -257,6 +257,7 @@ pub enum EditorTab {
     ModelEntityList,   // right side,
     Viewport,          // middle,
     ErrorConsole,
+    Console,
     Plugin(usize),
 }
 
diff --git a/crates/eucalyptus-core/src/ui.rs b/crates/eucalyptus-core/src/ui.rs
index bd919e3..91ed930 100644
--- a/crates/eucalyptus-core/src/ui.rs
+++ b/crates/eucalyptus-core/src/ui.rs
@@ -41,12 +41,11 @@ pub struct UiContext {
 pub fn poll() {
     UI_CONTEXT.with(|v| {
         let ctx = v.borrow();
-        let _yakui = ctx.yakui_state.lock();
         let instructions = ctx.instruction_set.lock().drain(..).collect::<Vec<UIInstruction>>();
         for i in instructions {
             match i {
                 UIInstruction::StartColumn => {
-                    
+                        
                 },
                 UIInstruction::EndColumn => {
 
diff --git a/crates/eucalyptus-editor/src/editor/console.rs b/crates/eucalyptus-editor/src/editor/console.rs
new file mode 100644
index 0000000..d8cabad
--- /dev/null
+++ b/crates/eucalyptus-editor/src/editor/console.rs
@@ -0,0 +1,88 @@
+use std::io::{BufRead, BufReader};
+use std::net::{TcpListener, TcpStream};
+use std::sync::Arc;
+use parking_lot::Mutex;
+
+pub struct EucalyptusConsole {
+    pub buffer: Arc<Mutex<Vec<String>>>,
+    pub history: Vec<String>,
+
+    pub show_info: bool,
+    pub show_warning: bool,
+    pub show_error: bool,
+    pub show_debug: bool,
+    pub show_trace: bool,
+    pub auto_scroll: bool,
+}
+
+impl EucalyptusConsole {
+    /// Creates a new instance of a [EucalyptusConsole], and
+    pub fn new(port: Option<&str>) -> Self {
+        let result = Self {
+            buffer: Arc::new(Default::default()),
+            history: vec![],
+            show_info: true,
+            show_warning: true,
+            show_error: true,
+            show_debug: false,
+            show_trace: false,
+            auto_scroll: true,
+        };
+
+        let buf_clone = result.buffer.clone();
+        let addr = format!("127.0.0.1:{}", port.unwrap_or("56624"));
+        std::thread::spawn(move || {
+            let listener = TcpListener::bind(&addr).unwrap();
+
+            log::info!("eucalyptus-editor debug console started at {}", addr);
+
+            loop {
+                for stream in listener.incoming() {
+                    match stream {
+                        Ok(stream) => {
+                            let buf_clone = buf_clone.clone();
+                            std::thread::spawn(move || {
+                                EucalyptusConsole::handle_client(stream, buf_clone)
+                            });
+                        }
+                        Err(e) => {
+                            eprintln!("Connection failed: {}", e);
+                        }
+                    }
+                }
+            }
+        });
+
+        result
+    }
+
+    fn handle_client(stream: TcpStream, buf: Arc<Mutex<Vec<String>>>) {
+        let peer_addr = stream.peer_addr().unwrap();
+        println!("New connection from: {}", peer_addr);
+
+        let reader = BufReader::new(stream);
+
+        for line in reader.lines() {
+            match line {
+                Ok(text) => {
+                    buf.lock().push(text);
+                }
+                Err(e) => {
+                    buf.lock().push(format!("Error reading from {}: {}", peer_addr, e));
+                    break;
+                }
+            }
+        }
+
+        println!("Connection closed: {}", peer_addr);
+    }
+
+    /// Drains all from the thread-safe buffer and adds to the history, while returning that value.
+    ///
+    /// It is recommended to use this function.
+    pub fn take(&mut self) -> Vec<String> {
+        let buf = self.buffer.lock().drain(..).collect::<Vec<String>>();
+        buf.iter().for_each(|v| self.history.push(v.clone()));
+        buf
+    }
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/editor/console_error.rs b/crates/eucalyptus-editor/src/editor/console_error.rs
index ed7362b..edf6c8f 100644
--- a/crates/eucalyptus-editor/src/editor/console_error.rs
+++ b/crates/eucalyptus-editor/src/editor/console_error.rs
@@ -1,6 +1,7 @@
 use std::path::PathBuf;
 
 pub enum ErrorLevel {
+    Info,
     Warn,
     Error,
 }
diff --git a/crates/eucalyptus-editor/src/editor/dock.rs b/crates/eucalyptus-editor/src/editor/dock.rs
index 90f604b..2af4bb5 100644
--- a/crates/eucalyptus-editor/src/editor/dock.rs
+++ b/crates/eucalyptus-editor/src/editor/dock.rs
@@ -36,6 +36,7 @@ use eucalyptus_core::physics::collider::{Collider, ColliderGroup};
 use eucalyptus_core::physics::kcc::KCC;
 use eucalyptus_core::physics::rigidbody::RigidBody;
 use eucalyptus_core::properties::CustomProperties;
+use crate::editor::console::EucalyptusConsole;
 
 pub struct EditorTabViewer<'a> {
     pub view: egui::TextureId,
@@ -53,6 +54,7 @@ pub struct EditorTabViewer<'a> {
     pub plugin_registry: &'a mut PluginRegistry,
     pub component_registry: &'a ComponentRegistry,
     pub build_logs: &'a mut Vec<String>,
+    pub eucalyptus_console: &'a mut EucalyptusConsole,
 
     // "wah wah its unsafe, its using raw pointers" shut the fuck up if it breaks i will know
     pub editor: *mut Editor,
@@ -191,7 +193,8 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                     "Unknown Plugin Name".into()
                 }
             }
-            EditorTab::ErrorConsole => "Error Console".into(),
+            EditorTab::ErrorConsole => "Build Output".into(),
+            EditorTab::Console => "Console".into(),
         }
     }
 
@@ -1012,29 +1015,32 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                     }
 
                     let mut list: Vec<ConsoleItem> = Vec::new();
-                    let index = 0;
-                    for line in log {
+                    for (index, line) in log.iter().enumerate() {
                         if line.contains("The required library") {
                             list.push(ConsoleItem {
                                 error_level: ErrorLevel::Error,
                                 msg: line.clone(),
                                 file_location: None,
                                 line_ref: None,
-                                id: index + 1,
+                                id: index as u64,
                             });
-                        }
-
-                        if let Some((error_level, path, loc)) = parse_compiler_location(line) {
+                        } else if let Some((error_level, path, loc)) = parse_compiler_location(line) {
                             list.push(ConsoleItem {
                                 error_level,
                                 msg: line.clone(),
                                 file_location: Some(path),
                                 line_ref: Some(loc),
-                                id: index + 1,
+                                id: index as u64,
+                            });
+                        } else {
+                            list.push(ConsoleItem {
+                                error_level: ErrorLevel::Info,
+                                msg: line.clone(),
+                                file_location: None,
+                                line_ref: None,
+                                id: index as u64,
                             });
                         }
-
-                        // thats it for now
                     }
                     list
                 }
@@ -1043,6 +1049,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
 
                 egui::ScrollArea::vertical()
                     .auto_shrink([false, false])
+                    .stick_to_bottom(true)
                     .show(ui, |ui| {
                         if logs.is_empty() {
                             ui.label("Build output will appear here once available.");
@@ -1050,64 +1057,141 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         }
 
                         for item in &logs {
-                            let (bg_color, text_color) = match item.error_level {
+                            let (bg_color, text_color, stroke_color) = match item.error_level {
                                 ErrorLevel::Error => (
                                     egui::Color32::from_rgb(60, 20, 20),
                                     egui::Color32::from_rgb(255, 200, 200),
+                                    egui::Color32::from_rgb(255, 200, 200),
                                 ),
                                 ErrorLevel::Warn => (
                                     egui::Color32::from_rgb(40, 40, 10),
                                     egui::Color32::from_rgb(255, 255, 200),
+                                    egui::Color32::from_rgb(255, 255, 200),
+                                ),
+                                ErrorLevel::Info => (
+                                    egui::Color32::TRANSPARENT,
+                                    ui.style().visuals.text_color(),
+                                    egui::Color32::TRANSPARENT,
                                 ),
                             };
 
-                            let available_width = ui.available_width();
-                            let frame = egui::Frame::new()
-                                .inner_margin(Margin::symmetric(8, 6))
-                                .fill(bg_color)
-                                .stroke(egui::Stroke::new(1.0, text_color));
-
-                            let response = frame
-                                .show(ui, |ui| {
-                                    ui.set_width(available_width - 10.0);
-                                    ui.horizontal(|ui| {
-                                        ui.label(RichText::new(&item.msg).color(text_color));
-                                    });
-                                })
-                                .response;
-
-                            if response.clicked() {
-                                log::debug!("Log item clicked: {}", &item.id);
-                                if let (Some(path), Some(loc)) =
-                                    (&item.file_location, &item.line_ref)
-                                {
-                                    let location_arg = format!("{}:{}", path.display(), loc);
-
-                                    match std::process::Command::new("code")
-                                        .args(["-g", &location_arg])
-                                        .spawn()
-                                        .map(|_| ())
+                            if matches!(item.error_level, ErrorLevel::Info) {
+                                ui.label(RichText::new(&item.msg).monospace());
+                            } else {
+                                let available_width = ui.available_width();
+                                let frame = egui::Frame::new()
+                                    .inner_margin(Margin::symmetric(8, 6))
+                                    .fill(bg_color)
+                                    .stroke(egui::Stroke::new(1.0, stroke_color));
+
+                                let response = frame
+                                    .show(ui, |ui| {
+                                        ui.set_width(available_width - 10.0);
+                                        ui.horizontal(|ui| {
+                                            ui.label(RichText::new(&item.msg).color(text_color).monospace());
+                                        });
+                                    })
+                                    .response;
+
+                                if response.clicked() {
+                                    log::debug!("Log item clicked: {}", &item.id);
+                                    if let (Some(path), Some(loc)) =
+                                        (&item.file_location, &item.line_ref)
                                     {
-                                        Ok(()) => {
-                                            log::info!(
-                                                "Launched Visual Studio Code at the error: {}",
-                                                &location_arg
-                                            );
-                                        }
-                                        Err(e) => {
-                                            warn!(
-                                                "Failed to open '{}' in VS Code: {}",
-                                                location_arg, e
-                                            );
+                                        let location_arg = format!("{}:{}", path.display(), loc);
+
+                                        match std::process::Command::new("code")
+                                            .args(["-g", &location_arg])
+                                            .spawn()
+                                            .map(|_| ())
+                                        {
+                                            Ok(()) => {
+                                                log::info!(
+                                                    "Launched Visual Studio Code at the error: {}",
+                                                    &location_arg
+                                                );
+                                            }
+                                            Err(e) => {
+                                                warn!(
+                                                    "Failed to open '{}' in VS Code: {}",
+                                                    location_arg, e
+                                                );
+                                            }
                                         }
                                     }
                                 }
                             }
-
-                            ui.add_space(4.0);
                         }
                     });
             }
+            EditorTab::Console => {
+                ui.heading("Console");
+                ui.separator();
+
+                ui.horizontal(|ui| {
+                    if ui.button("Clear").clicked() {
+                        self.eucalyptus_console.history.clear();
+                    }
+
+                    ui.separator();
+
+                    ui.checkbox(&mut self.eucalyptus_console.show_info, "Info");
+                    ui.checkbox(&mut self.eucalyptus_console.show_warning, "Warning");
+                    ui.checkbox(&mut self.eucalyptus_console.show_error, "Error");
+                    ui.checkbox(&mut self.eucalyptus_console.show_debug, "Debug");
+                    ui.checkbox(&mut self.eucalyptus_console.show_trace, "Trace");
+
+                    ui.separator();
+
+                    ui.checkbox(&mut self.eucalyptus_console.auto_scroll, "Auto-scroll");
+
+                    ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+                        ui.label(format!("Logs: {}", self.eucalyptus_console.history.len()));
+                    });
+                });
+
+                ui.separator();
+
+                let _ = self.eucalyptus_console.take();
+
+                let scroll = egui::ScrollArea::vertical()
+                    .auto_shrink([false, false])
+                    .stick_to_bottom(self.eucalyptus_console.auto_scroll);
+                
+                scroll.show(ui, |ui| {
+                    for log in &self.eucalyptus_console.history {
+                        let is_error = log.contains("[ERROR]") || log.contains("[FATAL]");
+                        let is_warn = log.contains("[WARN]");
+                        let is_debug = log.contains("[DEBUG]");
+                        let is_trace = log.contains("[TRACE]");
+                        let is_info = !is_error && !is_warn && !is_debug && !is_trace;
+
+                        if is_error && !self.eucalyptus_console.show_error { continue; }
+                        if is_warn && !self.eucalyptus_console.show_warning { continue; }
+                        if is_debug && !self.eucalyptus_console.show_debug { continue; }
+                        if is_trace && !self.eucalyptus_console.show_trace { continue; }
+                        if is_info && !self.eucalyptus_console.show_info { continue; }
+
+                        let color = if is_error {
+                            egui::Color32::from_rgb(255, 100, 100)
+                        } else if is_warn {
+                            egui::Color32::from_rgb(255, 200, 50)
+                        } else if is_debug {
+                            egui::Color32::from_rgb(100, 200, 255)
+                        } else if is_trace {
+                            egui::Color32::from_rgb(150, 150, 150)
+                        } else {
+                            egui::Color32::LIGHT_GRAY
+                        };
+
+                        ui.add(egui::Label::new(
+                            egui::RichText::new(log)
+                                .color(color)
+                                .monospace()
+                        ));
+                    }
+                });
+            }
         }
     }
 }
diff --git a/crates/eucalyptus-editor/src/editor/mod.rs b/crates/eucalyptus-editor/src/editor/mod.rs
index 5367519..ffe8fd9 100644
--- a/crates/eucalyptus-editor/src/editor/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/mod.rs
@@ -4,6 +4,7 @@ pub mod dock;
 pub mod input;
 pub mod scene;
 pub mod settings;
+mod console;
 
 pub(crate) use crate::editor::dock::*;
 
@@ -66,6 +67,7 @@ use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
 use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
 use eucalyptus_core::properties::CustomProperties;
 use crate::about::AboutWindow;
+use crate::editor::console::EucalyptusConsole;
 use crate::editor::settings::editor::{EditorSettingsWindow, EDITOR_SETTINGS};
 use crate::editor::settings::project::ProjectSettingsWindow;
 
@@ -110,6 +112,7 @@ pub struct Editor {
     pub(crate) editor_state: EditorState,
     pub gizmo_mode: EnumSet<GizmoMode>,
     pub gizmo_orientation: GizmoOrientation,
+    pub console: EucalyptusConsole,
 
     // might as well save some memory if its not required...
     // #[allow(unused)] // unused to allow for JVM to startup
@@ -231,6 +234,7 @@ impl Editor {
             editor_state: EditorState::Editing,
             gizmo_mode: EnumSet::empty(),
             gizmo_orientation: GizmoOrientation::Global,
+            console: EucalyptusConsole::new(None),
             play_mode_backup: None,
             input_state: Box::new(InputState::new()),
             light_cube_pipeline: None,
@@ -976,9 +980,12 @@ impl Editor {
                     if ui_window.button("Open Error Console").clicked() {
                         self.dock_state.push_to_focused_leaf(EditorTab::ErrorConsole);
                     }
+                    if ui_window.button("Open Debug Console").clicked() {
+                        self.dock_state.push_to_focused_leaf(EditorTab::Console);
+                    }
                     if self.plugin_registry.plugins.len() == 0 {
                         ui_window.label(
-                            egui::RichText::new("No plugins ")
+                            egui::RichText::new("No plugins")
                                 .color(ui_window.visuals().weak_text_color())
                         );
                     }
@@ -1100,6 +1107,7 @@ impl Editor {
                         editor: editor_ptr,
                         build_logs: &mut self.build_logs,
                         component_registry: &self.component_registry,
+                        eucalyptus_console: &mut self.console,
                     },
                 );
         });
diff --git a/crates/redback-runtime/src/lib.rs b/crates/redback-runtime/src/lib.rs
index 6520560..1108dc4 100644
--- a/crates/redback-runtime/src/lib.rs
+++ b/crates/redback-runtime/src/lib.rs
@@ -113,6 +113,7 @@ pub struct PlayMode {
 
 impl PlayMode {
     pub fn new(initial_scene: Option<String>) -> anyhow::Result<Self> {
+        eucalyptus_core::utils::start_deadlock_detector();
 
         let mut component_registry = ComponentRegistry::new();
 
diff --git a/scripting/commonMain/kotlin/com/dropbear/logging/LogLevel.kt b/scripting/commonMain/kotlin/com/dropbear/logging/LogLevel.kt
index 91fed03..7bb0017 100644
--- a/scripting/commonMain/kotlin/com/dropbear/logging/LogLevel.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/logging/LogLevel.kt
@@ -1,9 +1,9 @@
 package com.dropbear.logging
 
-enum class LogLevel {
-    TRACE,
-    DEBUG,
-    INFO,
-    WARN,
-    ERROR
+enum class LogLevel(val ansi: String) {
+    TRACE("\u001B[38;5;81m"),
+    DEBUG("\u001B[38;5;220m"),
+    INFO("\u001B[38;5;46m"),
+    WARN("\u001B[38;5;214m"),
+    ERROR("\u001B[31m"),
 }
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/logging/Logger.kt b/scripting/commonMain/kotlin/com/dropbear/logging/Logger.kt
index c80052d..51e655c 100644
--- a/scripting/commonMain/kotlin/com/dropbear/logging/Logger.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/logging/Logger.kt
@@ -5,7 +5,7 @@ import kotlin.jvm.JvmStatic
 
 @Suppress("unused")
 object Logger {
-    private var writer: LogWriter = StdoutWriter()
+    private var writer: LogWriter = SocketWriter()
     private var minLevel: LogLevel = LogLevel.INFO
     private var defaultTarget: String = "dropbear"
 
diff --git a/scripting/commonMain/kotlin/com/dropbear/logging/SocketWriter.kt b/scripting/commonMain/kotlin/com/dropbear/logging/SocketWriter.kt
new file mode 100644
index 0000000..42cc0e2
--- /dev/null
+++ b/scripting/commonMain/kotlin/com/dropbear/logging/SocketWriter.kt
@@ -0,0 +1,11 @@
+package com.dropbear.logging
+
+expect class SocketWriter(): LogWriter {
+    override fun log(
+        level: LogLevel,
+        target: String,
+        message: String,
+        file: String?,
+        line: Int?
+    )
+}
diff --git a/scripting/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt b/scripting/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt
index 845dfdc..c45640e 100644
--- a/scripting/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/logging/StdoutWriter.kt
@@ -5,6 +5,8 @@ import kotlinx.datetime.toLocalDateTime
 import kotlin.time.Clock
 
 class StdoutWriter: LogWriter {
+    private val reset = "\u001B[0m"
+
     override fun log(
         level: LogLevel,
         target: String,
@@ -17,8 +19,9 @@ class StdoutWriter: LogWriter {
         val timestamp = now.toLocalDateTime(timeZone)
         val location = if (file != null && line != null) "[$file:$line] " else ""
         when (level) {
-            LogLevel.ERROR, LogLevel.WARN -> error("[$timestamp] [$level] $location[$target] $message")
-            else -> println("[$timestamp] [$level] $location[$target] $message")
+            LogLevel.ERROR -> println("${level.ansi}[$timestamp] [$level] $location[$target] $message$reset")
+            LogLevel.WARN -> println("${level.ansi}[$timestamp] [$level] $location[$target] $message$reset")
+            else -> println("[$timestamp] [${level.ansi}$level$reset] $location[$target] $message")
         }
     }
 }
\ No newline at end of file
diff --git a/scripting/commonMain/kotlin/com/dropbear/ui/Ui.kt b/scripting/commonMain/kotlin/com/dropbear/ui/Ui.kt
index 2783f3d..4aca843 100644
--- a/scripting/commonMain/kotlin/com/dropbear/ui/Ui.kt
+++ b/scripting/commonMain/kotlin/com/dropbear/ui/Ui.kt
@@ -1,10 +1,5 @@
 package com.dropbear.ui
 
-import com.dropbear.math.Vector2d
-import com.dropbear.ui.primitive.Circle
-import com.dropbear.ui.primitive.Rectangle
-import com.dropbear.utils.ID
-
 /**
  * A command buffer to the 2D part of the game. Can be used for HUD and stuff.
  *
diff --git a/scripting/jvmMain/kotlin/com/dropbear/logging/SocketWriter.kt b/scripting/jvmMain/kotlin/com/dropbear/logging/SocketWriter.kt
new file mode 100644
index 0000000..8934e07
--- /dev/null
+++ b/scripting/jvmMain/kotlin/com/dropbear/logging/SocketWriter.kt
@@ -0,0 +1,57 @@
+package com.dropbear.logging
+
+import java.net.Socket
+import java.io.PrintWriter
+import java.util.concurrent.Executors
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toLocalDateTime
+import kotlin.time.Clock
+
+actual class SocketWriter actual constructor() : LogWriter {
+    private val executor = Executors.newSingleThreadExecutor()
+    private var writer: PrintWriter? = null
+    
+    init {
+        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
+            val stackTrace = java.io.StringWriter().apply {
+                throwable.printStackTrace(java.io.PrintWriter(this))
+            }.toString()
+            
+            log(LogLevel.ERROR, "UncaughtException", "Exception in thread \"${thread.name}\": $throwable\n$stackTrace", null, null)
+        }
+
+        executor.submit {
+            try {
+                // Connect to the editor console
+                val socket = Socket("127.0.0.1", 56624)
+                writer = PrintWriter(socket.getOutputStream(), true)
+            } catch (e: Exception) {
+                // Silently fail or log to stderr if connection fails
+                System.err.println("Failed to connect to logging socket: ${e.message}")
+            }
+        }
+    }
+
+    actual override fun log(
+        level: LogLevel,
+        target: String,
+        message: String,
+        file: String?,
+        line: Int?
+    ) {
+        val now = Clock.System.now()
+        val timeZone = TimeZone.currentSystemDefault()
+        val timestamp = now.toLocalDateTime(timeZone)
+        val location = if (file != null && line != null) "[$file:$line] " else ""
+        
+        val formatted = "[$timestamp] [$level] $location[$target] $message"
+        
+        executor.submit {
+            try {
+                writer?.println(formatted)
+            } catch (e: Exception) {
+                // Ignore write errors to avoid crashing
+            }
+        }
+    }
+}
diff --git a/scripting/jvmMain/kotlin/com/dropbear/ui/Ui.jvm.kt b/scripting/jvmMain/kotlin/com/dropbear/ui/Ui.jvm.kt
deleted file mode 100644
index 3101b8e..0000000
--- a/scripting/jvmMain/kotlin/com/dropbear/ui/Ui.jvm.kt
+++ /dev/null
@@ -1 +0,0 @@
-package com.dropbear.ui
\ No newline at end of file
diff --git a/scripting/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt b/scripting/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
index bdc6f7a..2663baa 100644
--- a/scripting/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
+++ b/scripting/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
@@ -33,6 +33,8 @@ actual class NativeEngine {
         this.physicsEngineHandle = ctx?.physics_engine?.rawValue?.let { interpretCPointer(it) }
         this.uiBufferHandle = ctx?.graphics?.rawValue?.let { interpretCPointer(it) }
 
+        Logger.init(com.dropbear.logging.SocketWriter())
+
         // if release, always enable exceptionOnError
         if (!Platform.isDebugBinary) {
             exceptionOnError = true
diff --git a/scripting/nativeMain/kotlin/com/dropbear/logging/SocketWriter.kt b/scripting/nativeMain/kotlin/com/dropbear/logging/SocketWriter.kt
new file mode 100644
index 0000000..62ce3de
--- /dev/null
+++ b/scripting/nativeMain/kotlin/com/dropbear/logging/SocketWriter.kt
@@ -0,0 +1,14 @@
+package com.dropbear.logging
+
+actual class SocketWriter actual constructor() : LogWriter {
+    actual override fun log(
+        level: LogLevel,
+        target: String,
+        message: String,
+        file: String?,
+        line: Int?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+}
diff --git a/scripting/nativeMain/kotlin/com/dropbear/ui/Ui.native.kt b/scripting/nativeMain/kotlin/com/dropbear/ui/Ui.native.kt
deleted file mode 100644
index 766fce1..0000000
--- a/scripting/nativeMain/kotlin/com/dropbear/ui/Ui.native.kt
+++ /dev/null
@@ -1 +0,0 @@
-package com.dropbear.ui