]> git.unchartedbackwaters.co.uk Git - francis/winuae.git/commitdiff
Enhance consolehook: thread safety, CLI, config, shutdown
authorDimitris Panokostas <midwan@gmail.com>
Thu, 15 Jan 2026 01:21:34 +0000 (02:21 +0100)
committerDimitris Panokostas <midwan@gmail.com>
Thu, 15 Jan 2026 01:21:34 +0000 (02:21 +0100)
Significantly improve consolehook (Amiga console.device emulation) for headless/CLI use:
- Add thread-safe input ring buffer and input thread for non-blocking reads.
- Implement graceful shutdown of input thread on reset/quit.
- Allow custom root path via new `-cli[=path]` command-line option.
- Update emulation prefs for better compatibility and expand ROM selection.
- Normalize console output, add safety checks, and update API signatures.
- Export `set_console_input_mode` for Windows.
- Improve GitHub Actions workflow for Windows builds.
These changes make console emulation more robust, responsive, and configurable.

.github/workflows/build-winuae-binary.yml
consolehook.cpp
include/consolehook.h
include/sysdeps.h
main.cpp
od-win32/writelog.cpp

index 298133db5c5b6c7a37d9c973484505d22bc39d8b..47d7fd10b48b569d4dddea8731d69f2bbea6ac87 100644 (file)
+@@ -0,0 +1,146 @@
 name: Build WinUAE binary
 
 on:
   workflow_dispatch:
   push:
-    branches: 
+    branches:
       - master
   pull_request:
     branches:
       - master
-      
+
 env:
   SOLUTION_FILE_PATH: od-win32\\winuae_msvc15
 
 jobs:
   Build-WinUAE-32bit-binary:
-    runs-on: windows-latest
+    runs-on: windows-2026
 
     steps:
-    - uses: actions/checkout@v4
-
-    - name: Add MSBuild to PATH
-      uses: microsoft/setup-msbuild@v2
-      with:
-        vs-version: '[18.0]'
-
-    # Running roughly step 4 of README.md
-    - name: Download WinUAE includes and libs
-      shell: powershell
-      run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
-
-    - name: Unpack WinUAE includes and libs to C:\\dev
-      uses: ihiroky/extract-action@v1
-      with:
-        file_path: winuaeinclibs.zip
-        extract_dir: C:\\dev
-
-    # Running roughly step 6 of README.md
-    - name: Download AROS ROM cpp
-      shell: powershell
-      run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
-
-    - name: Unpack AROS ROM cpp
-      uses: ihiroky/extract-action@v1
-      with:
-        file_path: aros.rom.cpp.zip
-        extract_dir: .
-
-    # Running roughly step 7 of README.md
-    - name: Add NASM to PATH
-      uses: ilammy/setup-nasm@v1.5.1
-
-    # Running roughly step 12 of README.md
-    - name: Build Win32 FullRelease
-      working-directory: ${{env.GITHUB_WORKSPACE}}
-      # Add additional options to the MSBuild command line here (like platform or verbosity level).
-      # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
-      run: msbuild /m /p:Platform=Win32 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
-
-    - uses: actions/upload-artifact@v4
-      with:
-        name: WinUAE 32-bit
-        path: D:\\Amiga
+      - uses: actions/checkout@v4
+
+      - name: Diagnostics (VS instances)
+        shell: powershell
+        run: |
+          if (Test-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe") {
+            & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -all -prerelease -products * -format json
+          } elseif (Get-Command vswhere.exe -ErrorAction SilentlyContinue) {
+            vswhere.exe -all -prerelease -products * -format json
+          } else {
+            Write-Host "vswhere.exe not found"
+          }
+
+      - name: Add MSBuild to PATH
+        uses: microsoft/setup-msbuild@v2
+        with:
+          vs-version: '18.0'
+
+      - name: Diagnostics (MSBuild)
+        shell: powershell
+        run: |
+          Get-Command msbuild -ErrorAction SilentlyContinue | Format-List *
+          msbuild -version
+
+      # Running roughly step 4 of README.md
+      - name: Download WinUAE includes and libs
+        shell: powershell
+        run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
+
+      - name: Unpack WinUAE includes and libs to C:\\dev
+        uses: ihiroky/extract-action@v1
+        with:
+          file_path: winuaeinclibs.zip
+          extract_dir: C:\\dev
+
+      # Running roughly step 6 of README.md
+      - name: Download AROS ROM cpp
+        shell: powershell
+        run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
+
+      - name: Unpack AROS ROM cpp
+        uses: ihiroky/extract-action@v1
+        with:
+          file_path: aros.rom.cpp.zip
+          extract_dir: .
+
+      # Running roughly step 7 of README.md
+      - name: Add NASM to PATH
+        uses: ilammy/setup-nasm@v1.5.1
+
+      # Running roughly step 12 of README.md
+      - name: Build Win32 FullRelease
+        working-directory: ${{env.GITHUB_WORKSPACE}}
+        # Add additional options to the MSBuild command line here (like platform or verbosity level).
+        # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
+        run: msbuild /m /p:Platform=Win32 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: WinUAE 32-bit
+          path: D:\\Amiga
 
   Build-WinUAE-64bit-binary:
-    runs-on: windows-latest
+    runs-on: windows-2026
 
     steps:
-    - uses: actions/checkout@v4
-
-    - name: Add MSBuild to PATH
-      uses: microsoft/setup-msbuild@v2
-      with:
-        vs-version: '[18.0]'
-
-    # Running roughly step 4 of README.md
-    - name: Download WinUAE includes and libs
-      shell: powershell
-      run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
-
-    - name: Unpack WinUAE includes and libs to C:\\dev
-      uses: ihiroky/extract-action@v1
-      with:
-        file_path: winuaeinclibs.zip
-        extract_dir: C:\\dev
-
-    # Running roughly step 6 of README.md
-    - name: Download AROS ROM cpp
-      shell: powershell
-      run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
-
-    - name: Unpack AROS ROM cpp
-      uses: ihiroky/extract-action@v1
-      with:
-        file_path: aros.rom.cpp.zip
-        extract_dir: .
-
-    # Running roughly step 7 of README.md
-    - name: Add NASM to PATH
-      uses: ilammy/setup-nasm@v1.5.1
-
-    # Running roughly step 12 of README.md
-    - name: Build x64 FullRelease
-      working-directory: ${{env.GITHUB_WORKSPACE}}
-      # Add additional options to the MSBuild command line here (like platform or verbosity level).
-      # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
-      run: msbuild /m /p:Platform=x64 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
-
-    - uses: actions/upload-artifact@v4
-      with:
-        name: WinUAE 64-bit
-        path: D:\\Amiga
+      - uses: actions/checkout@v4
+
+      - name: Diagnostics (VS instances)
+        shell: powershell
+        run: |
+          if (Test-Path "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe") {
+            & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -all -prerelease -products * -format json
+          } elseif (Get-Command vswhere.exe -ErrorAction SilentlyContinue) {
+            vswhere.exe -all -prerelease -products * -format json
+          } else {
+            Write-Host "vswhere.exe not found"
+          }
+
+      - name: Add MSBuild to PATH
+        uses: microsoft/setup-msbuild@v2
+        with:
+          vs-version: '18.0'
+
+      - name: Diagnostics (MSBuild)
+        shell: powershell
+        run: |
+          Get-Command msbuild -ErrorAction SilentlyContinue | Format-List *
+          msbuild -version
+
+      # Running roughly step 4 of README.md
+      - name: Download WinUAE includes and libs
+        shell: powershell
+        run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/winuaeinclibs.zip" -OutFile "winuaeinclibs.zip"
+
+      - name: Unpack WinUAE includes and libs to C:\\dev
+        uses: ihiroky/extract-action@v1
+        with:
+          file_path: winuaeinclibs.zip
+          extract_dir: C:\\dev
+
+      # Running roughly step 6 of README.md
+      - name: Download AROS ROM cpp
+        shell: powershell
+        run: Invoke-WebRequest -Uri "https://download.abime.net/winuae/files/b/aros.rom.cpp.zip" -OutFile "aros.rom.cpp.zip"
+
+      - name: Unpack AROS ROM cpp
+        uses: ihiroky/extract-action@v1
+        with:
+          file_path: aros.rom.cpp.zip
+          extract_dir: .
+
+      # Running roughly step 7 of README.md
+      - name: Add NASM to PATH
+        uses: ilammy/setup-nasm@v1.5.1
+
+      # Running roughly step 12 of README.md
+      - name: Build x64 FullRelease
+        working-directory: ${{env.GITHUB_WORKSPACE}}
+        # Add additional options to the MSBuild command line here (like platform or verbosity level).
+        # See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
+        run: msbuild /m /p:Platform=x64 /p:Configuration=FullRelease ${{env.SOLUTION_FILE_PATH}}
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: WinUAE 64-bit
+          path: D:\\Amiga
index 06659fb0ba1b50c341973f5d5edd6a5f97675874..db5c0dfd22ff064c0dcf91f90052f61b32f137cb 100644 (file)
@@ -1,5 +1,3 @@
-
-
 #include "sysconfig.h"
 #include "sysdeps.h"
 
@@ -9,17 +7,94 @@
 #include "rommgr.h"
 #include "uae.h"
 #include "threaddep/thread.h"
-#include "keybuf.h"
-
+#include "fsdb.h"
+#include "custom.h"
 #include "consolehook.h"
 
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+extern int consoleopen;
+
+#define CONSOLEHOOK_DEBUG 0
+
 static uaecptr beginio;
 
-void consolehook_config (struct uae_prefs *p)
+static uae_sem_t console_in_sem;
+static uae_atomic console_in_init;
+static uae_atomic console_thread_started;
+static uae_atomic console_stop;
+
+static unsigned char console_in_buf[8192];
+static volatile uae_u32 console_in_rp;
+static volatile uae_u32 console_in_wp;
+
+static void console_in_init_once(void)
 {
-       struct uaedev_config_info ci = { 0 };
-       int roms[] = { 15, 31, 16, 46, -1 };
+       if (console_in_init)
+               return;
+       console_in_init = 1;
+       uae_sem_init(&console_in_sem, 0, 1);
+       console_in_rp = console_in_wp = 0;
+}
 
+static uae_u32 console_in_avail_nolock(void)
+{
+       return (console_in_wp - console_in_rp) & (sizeof console_in_buf - 1);
+}
+
+static void console_in_push(unsigned char b)
+{
+       uae_sem_wait(&console_in_sem);
+       uae_u32 next = (console_in_wp + 1) & (sizeof console_in_buf - 1);
+       if (next != console_in_rp) {
+               console_in_buf[console_in_wp] = b;
+               console_in_wp = next;
+       }
+       uae_sem_post(&console_in_sem);
+#if CONSOLEHOOK_DEBUG
+       write_log(_T("[consolehook] push %02X avail=%u\n"), b, console_in_avail_nolock());
+#endif
+}
+
+static uae_u32 console_in_pop(unsigned char *dst, uae_u32 maxlen)
+{
+       uae_u32 got = 0;
+       uae_sem_wait(&console_in_sem);
+       while (got < maxlen && console_in_rp != console_in_wp) {
+               dst[got++] = console_in_buf[console_in_rp];
+               console_in_rp = (console_in_rp + 1) & (sizeof console_in_buf - 1);
+       }
+       uae_sem_post(&console_in_sem);
+       return got;
+}
+
+/* CMD_READ must not block: it is called on the emulation thread. */
+
+void consolehook_shutdown(void)
+{
+       console_stop = 1;
+}
+
+void consolehook_config (struct uae_prefs *p, const TCHAR *custom_path)
+{
+       struct uaedev_config_info ci = { 0 };
+       int roms[8];
+       roms[0] = 11;
+       roms[1] = 15;
+#ifdef AMIBERRY
+       roms[2] = 276;
+       roms[3] = 281;
+       roms[4] = 286;
+       roms[5] = 291;
+       roms[6] = 304;
+       roms[7] = -1;
+#endif
+
+       consoleopen = 0;
        default_prefs (p, true, 0);
        //p->headless = 1;
        p->produce_sound = 0;
@@ -33,11 +108,15 @@ void consolehook_config (struct uae_prefs *p)
        configure_rom (p, roms, 0);
        p->cpu_model = 68020;
        p->fpu_model = 68882;
+       p->chipset_mask = CSMASK_AGA | CSMASK_ECS_AGNUS | CSMASK_ECS_DENISE;
+       p->cpu_compatible = p->address_space_24 = false;
        p->m68k_speed = -1;
-       p->cachesize = 8192;
-       p->cpu_compatible = 0;
-       p->address_space_24 = 0;
-       p->chipmem.size = 0x00200000;
+#ifdef JIT
+       p->cachesize = 16384;
+#else
+       p->cachesize = 0;
+#endif
+       p->chipmem.size = 0x200000;
        p->fastmem[0].size = 0x00800000;
        p->bogomem.size = 0;
        p->nr_floppies = 1;
@@ -49,8 +128,27 @@ void consolehook_config (struct uae_prefs *p)
        p->turbo_emulation = 0;
        //p->win32_automount_drives = 2;
        //p->win32_automount_cddrives = 2;
-
-       _tcscpy (ci.rootdir, _T("."));
+       p->cs_compatible = CP_A1200;
+       built_in_chipset_prefs (p);
+
+       uci_set_defaults(&ci, false);
+       if (custom_path && custom_path[0]) {
+               // Use the custom path provided via command line
+               my_canonicalize_path(custom_path, ci.rootdir, MAX_DPATH);
+       } else {
+               // Use current working directory as before
+               TCHAR cwd[MAX_DPATH];
+               cwd[0] = 0;
+#ifdef _WIN32
+               GetCurrentDirectory(MAX_DPATH, cwd);
+#else
+               getcwd(cwd, MAX_DPATH);
+#endif
+               if (cwd[0])
+                       my_canonicalize_path(cwd, ci.rootdir, MAX_DPATH);
+               else
+                       _tcscpy(ci.rootdir, _T("."));
+       }
        _tcscpy (ci.volname, _T("CLIBOOT"));
        _tcscpy (ci.devname, _T("DH0"));
        ci.bootpri = 15;
@@ -61,16 +159,22 @@ void consolehook_config (struct uae_prefs *p)
 static void console_thread (void *v)
 {
        uae_set_thread_priority (NULL, 1);
+       console_in_init_once();
+       set_console_input_mode(0);
        for (;;) {
+               if (console_stop)
+                       break;
                TCHAR wc = console_getch ();
-               char c[2];
-
-               c[0] = 0;
-               c[1] = 0;
-               ua_copy (c, 1, &wc);
-               record_key_direct((0x10 << 1) | 0, false);
-               record_key_direct((0x10 << 1) | 1, false);
+               if (!wc)
+                       continue;
+               char c = (char)wc;
+               /* Normalize host console keys to what Amiga console.device expects. */
+               if (c == '\r')
+                       c = '\n';
+               console_in_push((unsigned char)c);
        }
+       set_console_input_mode(1);
+       console_thread_started = 0;
 }
 
 int consolehook_activate (void)
@@ -82,40 +186,62 @@ void consolehook_ret(TrapContext *ctx, uaecptr condev, uaecptr oldbeginio)
 {
        beginio = oldbeginio;
        write_log (_T("console.device at %08X\n"), condev);
-
-       uae_start_thread (_T("consolereader"), console_thread, NULL, NULL);
+       open_console();
+       console_in_init_once();
+       if (!console_thread_started) {
+               console_thread_started = 1;
+               console_stop = 0;
+               uae_start_thread (_T("consolereader"), console_thread, NULL, NULL);
+       }
 }
 
 uaecptr consolehook_beginio(TrapContext *ctx, uaecptr request)
 {
        uae_u32 io_data = trap_get_long(ctx, request + 40); // 0x28
        uae_u32 io_length = trap_get_long(ctx, request + 36); // 0x24
-       uae_u32 io_actual = trap_get_long(ctx, request + 32); // 0x20
-       uae_u32 io_offset = trap_get_long(ctx, request + 44); // 0x2c
+       (void)trap_get_long(ctx, request + 32); // io_actual (unused)
+       (void)trap_get_long(ctx, request + 44); // io_offset (unused)
        uae_u16 cmd = trap_get_word(ctx, request + 28);
 
        if (cmd == CMD_WRITE) {
-               int len = io_length;
-               char *dbuf;
-               TCHAR *buf;
-               if (len == -1) {
-                       dbuf = xmalloc(char, MAX_DPATH);
-                       trap_get_string(ctx, dbuf, io_data, MAX_DPATH);
-                       len = uaestrlen(dbuf);
-               } else {
-                       dbuf = xmalloc(char, len);
+               open_console();
+               int len = (int)io_length;
+               if (len < 0)
+                       len = 0;
+               if (len > 1024 * 1024)
+                       len = 1024 * 1024;
+               if (len > 0 && io_data) {
+                       char *dbuf = xmalloc(char, len);
+                       if (!dbuf)
+                               return beginio;
                        trap_get_bytes(ctx, dbuf, io_data, len);
+
+                       for (int i = 0; i < len; i++) {
+                               TCHAR ch = (TCHAR)dbuf[i];
+                               if (ch == '\n')
+                                       console_out_f(_T("\r"));
+                               console_out_f(_T("%c"), ch);
+                       }
+                       console_flush();
+                       xfree(dbuf);
                }
-               buf = xmalloc(TCHAR, len + 1);
-               au_copy(buf, len, dbuf);
-               buf[len] = 0;
-               f_out(stdout, _T("%s"), buf);
-               xfree(buf);
-               xfree(dbuf);
        } else if (cmd == CMD_READ) {
-
-               write_log (_T("%08x: CMD=%d LEN=%d OFF=%d ACT=%d\n"), request, cmd, io_length, io_offset, io_actual);
-
+               console_in_init_once();
+               uae_u32 maxlen = io_length;
+               if ((uae_s32)maxlen < 0)
+                       maxlen = 0;
+               if (maxlen > 4096)
+                       maxlen = 4096;
+               unsigned char tmp[4096];
+               uae_u32 got = 0;
+               if (maxlen && io_data) // Fix: check io_data (buf) is not NULL before use
+                       got = console_in_pop(tmp, maxlen);
+#if CONSOLEHOOK_DEBUG
+               write_log(_T("[consolehook] CMD_READ max=%u got=%u\n"), maxlen, got);
+#endif
+               if (got && io_data) // Fix: check io_data (buf) is not NULL before use
+                       trap_put_bytes(ctx, tmp, io_data, got);
+               trap_put_long(ctx, request + 32, got); // io_actual
        }
        return beginio;
 }
index d119644c1c353de9d4f8508599e43e5d69b5830d..60767f5c8c43d735606cb636a1040d78fd49892b 100644 (file)
@@ -6,6 +6,7 @@
 int consolehook_activate(void);
 void consolehook_ret(TrapContext *ctx, uaecptr condev, uaecptr oldbeginio);
 uaecptr consolehook_beginio(TrapContext *ctx, uaecptr request);
-void consolehook_config(struct uae_prefs *p);
+void consolehook_config(struct uae_prefs *p, const TCHAR *custom_path);
+void consolehook_shutdown(void);
 
 #endif /* UAE_CONSOLEHOOK_H */
index dae18aabbc285ccecd41071890bb9e8e0a39a9de..6f4d2a672b62150d3d9dc3c7aa4a4cf877f5f5ed 100644 (file)
@@ -460,6 +460,7 @@ extern void open_console(void);
 extern void reopen_console(void);
 extern void activate_console(void);
 extern void deactivate_console(void);
+extern void set_console_input_mode(int);
 extern void console_out (const TCHAR *);
 extern void console_out_f (const TCHAR *, ...);
 extern void console_flush (void);
index e2f69ed667de4e4b1b5508e9976ecf933f512aa9..40f7b67c567a329a9a937cb793960f2e07b39216 100644 (file)
--- a/main.cpp
+++ b/main.cpp
@@ -63,6 +63,7 @@ bool no_gui = 0, quit_to_gui = 0;
 bool cloanto_rom = 0;
 bool kickstart_rom = 1;
 bool console_emulation = 0;
+TCHAR console_path[MAX_DPATH] = { 0 };
 
 struct gui_info gui_data;
 
@@ -837,6 +838,7 @@ void uae_reset (int hardreset, int keyboardreset)
        currprefs.quitstatefile[0] = changed_prefs.quitstatefile[0] = 0;
 
        if (quit_program == 0) {
+               consolehook_shutdown();
                quit_program = -UAE_RESET;
                if (keyboardreset)
                        quit_program = -UAE_RESET_KEYBOARD;
@@ -851,6 +853,7 @@ void uae_quit (void)
 #ifdef DEBUGGER
        deactivate_debugger ();
 #endif
+       consolehook_shutdown();
        if (quit_program != -UAE_QUIT)
                quit_program = -UAE_QUIT;
        target_quit ();
@@ -965,6 +968,25 @@ static void parse_cmdline (int argc, TCHAR **argv)
        started = true;
 
        for (i = 1; i < argc; i++) {
+               if (_tcsncmp(argv[i], _T("-cli="), 5) == 0 || _tcsncmp(argv[i], _T("--cli="), 6) == 0) {
+                       console_emulation = 1;
+                       TCHAR *path = _tcschr(argv[i], _T('=')) + 1;
+                       if (path && path[0]) {
+                               _tcsncpy(console_path, path, MAX_DPATH - 1);
+                               console_path[MAX_DPATH - 1] = 0;
+                       }
+                       continue;
+               }
+               if (_tcscmp(argv[i], _T("-cli")) == 0 || _tcscmp(argv[i], _T("--cli")) == 0) {
+                       console_emulation = 1;
+                       // Check if next argument is a path (not starting with '-')
+                       if (i + 1 < argc && argv[i + 1][0] != _T('-')) {
+                               i++;
+                               _tcsncpy(console_path, argv[i], MAX_DPATH - 1);
+                               console_path[MAX_DPATH - 1] = 0;
+                       }
+                       continue;
+               }
                if (!_tcsncmp (argv[i], _T("-diskswapper="), 13)) {
                        TCHAR *txt = parsetextpath (argv[i] + 13);
                        parse_diskswapper (txt);
@@ -1169,7 +1191,7 @@ static int real_main2 (int argc, TCHAR **argv)
        }
 
        if (console_emulation) {
-               consolehook_config (&currprefs);
+               consolehook_config (&currprefs, console_path[0] ? console_path : NULL);
                fixup_prefs (&currprefs, true);
        }
 
index 9fef564268cd2a3c87aaef11fb8d914ef49587a1..f009d6389361f705b88439ecbf1f7279817a3b91 100644 (file)
@@ -88,7 +88,7 @@ static HWND myGetConsoleWindow (void)
        return GetConsoleWindow ();
 }
 
-static void set_console_input_mode(int line)
+void set_console_input_mode(int line)
 {
        if (console_input_linemode < 0)
                return;