Browse Source

Migrated commits from @JimMarlowe for setting up the debug socket server

Shaddock Heath 8 years ago
parent
commit
5b64e44bdb

BIN
Resources/EditorData/AtomicEditor/editor/skin/jsdebug.png


+ 3 - 0
Resources/EditorData/AtomicEditor/editor/skin/skin.tb.txt

@@ -109,6 +109,9 @@ elements
 	StepButton
 	StepButton
 		bitmap step.png
 		bitmap step.png
 
 
+	DebugButton
+		bitmap jsdebug.png
+
 	BlendleftButton
 	BlendleftButton
 		bitmap blendleft.png
 		bitmap blendleft.png
 
 

BIN
Resources/EditorData/AtomicEditor/editor/skin_light/jsdebug.png


+ 3 - 0
Resources/EditorData/AtomicEditor/editor/skin_light/skin.tb.txt

@@ -109,6 +109,9 @@ elements
 	StepButton
 	StepButton
 		bitmap step.png
 		bitmap step.png
 
 
+	DebugButton
+		bitmap jsdebug.png
+
 	PowerOffButton
 	PowerOffButton
 		bitmap power_off.png
 		bitmap power_off.png
 
 

+ 6 - 1
Resources/EditorData/AtomicEditor/editor/ui/maintoolbar.tb.txt

@@ -18,6 +18,11 @@ TBLayout: distribution: gravity, spacing: 4
 		TBSkinImage: skin: StepButton, id: skin_image
 		TBSkinImage: skin: StepButton, id: skin_image
 		id maintoolbar_step
 		id maintoolbar_step
 		tooltip Step paused project 1 frame
 		tooltip Step paused project 1 frame
+	TBButton
+		@include definitions>menubutton
+		TBSkinImage: skin: DebugButton, id: skin_image
+		id maintoolbar_jsdebug
+		tooltip Invoke debugger on javascript project
 	TBButton: toggle-mode: 1
 	TBButton: toggle-mode: 1
 		@include definitions>menubutton
 		@include definitions>menubutton
 		TBSkinImage: skin: 3DTranslateBitmap
 		TBSkinImage: skin: 3DTranslateBitmap
@@ -37,4 +42,4 @@ TBLayout: distribution: gravity, spacing: 4
 		lp: width: 64
 		lp: width: 64
 		text: "Local"
 		text: "Local"
 		id 3d_axismode
 		id 3d_axismode
-		tooltip Effect world or local object
+		tooltip Effect world or local object

+ 2 - 0
Script/AtomicEditor/ui/EditorStrings.ts

@@ -38,6 +38,7 @@ export enum StringID {
     ShortcutPlay,
     ShortcutPlay,
     ShortcutPause,
     ShortcutPause,
     ShortcutStep,
     ShortcutStep,
+    ShortcutJSDebug,
     ShortcutPlayDebug,
     ShortcutPlayDebug,
     ShortcutBuild,
     ShortcutBuild,
     ShortcutBuildSettings,
     ShortcutBuildSettings,
@@ -94,6 +95,7 @@ export class EditorString {
         lookup[StringID.ShortcutPlay] = shortcutKey + "P";
         lookup[StringID.ShortcutPlay] = shortcutKey + "P";
         lookup[StringID.ShortcutPause] = shortcutKey + "U";
         lookup[StringID.ShortcutPause] = shortcutKey + "U";
         lookup[StringID.ShortcutStep] = "⇧" + shortcutKey + "U";
         lookup[StringID.ShortcutStep] = "⇧" + shortcutKey + "U";
+        lookup[StringID.ShortcutJSDebug] = shortcutKey + "J";
         lookup[StringID.ShortcutPlayDebug] = "⇧" + shortcutKey + "P";
         lookup[StringID.ShortcutPlayDebug] = "⇧" + shortcutKey + "P";
 
 
         lookup[StringID.ShortcutBuild] = shortcutKey + "B";
         lookup[StringID.ShortcutBuild] = shortcutKey + "B";

+ 6 - 0
Script/AtomicEditor/ui/MainToolbar.ts

@@ -31,6 +31,7 @@ class MainToolbar extends Atomic.UIWidget {
     playButton: Atomic.UIButton;
     playButton: Atomic.UIButton;
     pauseButton: Atomic.UIButton;
     pauseButton: Atomic.UIButton;
     stepButton: Atomic.UIButton;
     stepButton: Atomic.UIButton;
+    jsdebugButton: Atomic.UIButton;
 
 
     constructor(parent: Atomic.UIWidget) {
     constructor(parent: Atomic.UIWidget) {
 
 
@@ -50,6 +51,8 @@ class MainToolbar extends Atomic.UIWidget {
 
 
         this.stepButton = <Atomic.UIButton>this.getWidget("maintoolbar_step");
         this.stepButton = <Atomic.UIButton>this.getWidget("maintoolbar_step");
 
 
+        this.jsdebugButton = <Atomic.UIButton>this.getWidget("maintoolbar_jsdebug");
+
         this.translateButton.value = 1;
         this.translateButton.value = 1;
 
 
         parent.addChild(this);
         parent.addChild(this);
@@ -154,6 +157,9 @@ class MainToolbar extends Atomic.UIWidget {
             } else if (ev.target.id == "maintoolbar_step") {
             } else if (ev.target.id == "maintoolbar_step") {
                 EditorUI.getShortcuts().invokeStepPausedPlayer();
                 EditorUI.getShortcuts().invokeStepPausedPlayer();
                 return true;
                 return true;
+            } else if (ev.target.id == "maintoolbar_jsdebug") {
+                EditorUI.getShortcuts().invokePlayerJSDebug();
+                return true;
             }
             }
 
 
         }
         }

+ 6 - 0
Script/AtomicEditor/ui/Shortcuts.ts

@@ -102,6 +102,10 @@ class Shortcuts extends Atomic.ScriptObject {
         }
         }
     }
     }
 
 
+    invokePlayerJSDebug() {
+        Atomic.editorMode.playerJSDebug();
+    }
+
     invokeFormatCode() {
     invokeFormatCode() {
 
 
         var editor = EditorUI.getCurrentResourceEditor();
         var editor = EditorUI.getCurrentResourceEditor();
@@ -259,6 +263,8 @@ class Shortcuts extends Atomic.ScriptObject {
             }
             }
             else if (ev.key == Atomic.KEY_P) {
             else if (ev.key == Atomic.KEY_P) {
                 this.invokePlayOrStopPlayer();
                 this.invokePlayOrStopPlayer();
+            } else if (ev.key == Atomic.KEY_J) {
+                this.invokePlayerJSDebug ();
             } else if (ev.key == Atomic.KEY_B) {
             } else if (ev.key == Atomic.KEY_B) {
                 if (ev.qualifiers & Atomic.QUAL_SHIFT) {
                 if (ev.qualifiers & Atomic.QUAL_SHIFT) {
                     EditorUI.getModelOps().showBuildSettings();
                     EditorUI.getModelOps().showBuildSettings();

+ 5 - 0
Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts

@@ -92,6 +92,10 @@ class MainFrameMenu extends Atomic.ScriptObject {
                 EditorUI.getShortcuts().invokeStepPausedPlayer();
                 EditorUI.getShortcuts().invokeStepPausedPlayer();
                 return true;
                 return true;
             }
             }
+            if (refid == "edit js debug") {
+                EditorUI.getShortcuts().invokePlayerJSDebug();
+                return true;
+            }
 
 
             if (refid == "edit play debug") {
             if (refid == "edit play debug") {
                 EditorUI.getShortcuts().invokePlayOrStopPlayer(true);
                 EditorUI.getShortcuts().invokePlayOrStopPlayer(true);
@@ -396,6 +400,7 @@ var editItems = {
     "Play": ["edit play", StringID.ShortcutPlay],
     "Play": ["edit play", StringID.ShortcutPlay],
     "Pause/Resume": ["edit pause", StringID.ShortcutPause],
     "Pause/Resume": ["edit pause", StringID.ShortcutPause],
     "Step": ["edit step", StringID.ShortcutStep],
     "Step": ["edit step", StringID.ShortcutStep],
+    "Debug (JS Project)": ["edit js debug", StringID.ShortcutJSDebug],
     "Debug (C# Project)": ["edit play debug", StringID.ShortcutPlayDebug],
     "Debug (C# Project)": ["edit play debug", StringID.ShortcutPlayDebug],
     "-6": null,
     "-6": null,
     "Snap Settings": ["edit snap settings"]
     "Snap Settings": ["edit snap settings"]

+ 6 - 0
Source/Atomic/Core/CoreEvents.h

@@ -69,6 +69,12 @@ ATOMIC_EVENT(E_UPDATESPAUSEDRESUMED, UpdatesPaused)
 {
 {
     ATOMIC_PARAM(P_PAUSED, Paused);            // bool
     ATOMIC_PARAM(P_PAUSED, Paused);            // bool
 }
 }
+
+// we want the js debugger to connect to the player
+ATOMIC_EVENT(E_JSDEBUGGER, GoJSDebugger)
+{
+}
 // ATOMIC END
 // ATOMIC END
 
 
+
 }
 }

+ 833 - 0
Source/AtomicApp/Player/IPCPlayerApp.cpp

@@ -34,10 +34,816 @@
 #include <Atomic/UI/SystemUI/DebugHud.h>
 #include <Atomic/UI/SystemUI/DebugHud.h>
 
 
 #include "IPCPlayerApp.h"
 #include "IPCPlayerApp.h"
+#include <stdio.h>
+#include <stdio.h>
 
 
 namespace Atomic
 namespace Atomic
 {
 {
 
 
+
+
+IPCPlayerApp *arobj = NULL;
+
+void do_reconnect()  // hack to call object method
+{
+    if (arobj)
+        arobj->Reconnect();
+}
+
+
+#ifdef ATOMIC_PLATFORM_WINDOWS
+
+/*
+ *  Example debug transport using a Windows TCP socket
+ *
+ *  Provides a TCP server socket which a debug client can connect to.
+ *  After that data is just passed through.
+ *
+ *  https://msdn.microsoft.com/en-us/library/windows/desktop/ms737593(v=vs.85).aspx
+ *
+ *  Compiling 'duk' with debugger support using MSVC (Visual Studio):
+ *
+ *    > python2 tools\configure.py \
+ *          --output-directory prep
+ *          -DDUK_USE_DEBUGGER_SUPPORT -DDUK_USE_INTERRUPT_COUNTER
+ *    > cl /W3 /O2 /Feduk.exe \
+ *          /DDUK_CMDLINE_DEBUGGER_SUPPORT
+ *          /Iexamples\debug-trans-socket /Iprep
+ *          examples\cmdline\duk_cmdline.c
+ *          examples\debug-trans-socket\duk_trans_socket_windows.c
+ *          prep\duktape.c
+ *
+ *  With MinGW:
+ *
+ *    $ python2 tools\configure.py \
+ *          --output-directory prep
+ *          -DDUK_USE_DEBUGGER_SUPPORT -DDUK_USE_INTERRUPT_COUNTER
+ *    $ gcc -oduk.exe -Wall -O2 \
+ *          -DDUK_CMDLINE_DEBUGGER_SUPPORT \
+ *          -Iexamples/debug-trans-socket -Iprep \
+ *          examples/cmdline/duk_cmdline.c \
+ *          examples/debug-trans-socket/duk_trans_socket_windows.c \
+ *          prep/duktape.c -lm -lws2_32
+ */
+
+#undef UNICODE
+#if !defined(WIN32_LEAN_AND_MEAN)
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+/* MinGW workaround for missing getaddrinfo() etc:
+ * http://programmingrants.blogspot.fi/2009/09/tips-on-undefined-reference-to.html
+ */
+#if defined(__MINGW32__) || defined(__MINGW64__)
+#if !defined(_WIN32_WINNT)
+#define _WIN32_WINNT 0x0501
+#endif
+#endif
+
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <stdio.h>
+#include <string.h>
+#include "duktape.h"
+
+#if defined(_MSC_VER)
+#pragma comment (lib, "Ws2_32.lib")
+#endif
+
+#if !defined(DUK_DEBUG_PORT)
+#define DUK_DEBUG_PORT 9091
+#endif
+#if !defined(DUK_DEBUG_ADDRESS)
+#define DUK_DEBUG_ADDRESS "0.0.0.0"
+#endif
+#define DUK__STRINGIFY_HELPER(x) #x
+#define DUK__STRINGIFY(x) DUK__STRINGIFY_HELPER(x)
+
+#if 0
+#define DEBUG_PRINTS
+#endif
+
+static SOCKET server_sock = INVALID_SOCKET;
+static SOCKET client_sock = INVALID_SOCKET;
+static int wsa_inited = 0;
+
+/*
+ *  Transport init and finish
+ */
+
+void duk_trans_socket_init(void) {
+    WSADATA wsa_data;
+    struct addrinfo hints;
+    struct addrinfo *result = NULL;
+    int rc;
+
+    memset((void *) &wsa_data, 0, sizeof(wsa_data));
+    memset((void *) &hints, 0, sizeof(hints));
+
+    rc = WSAStartup(MAKEWORD(2, 2), &wsa_data);
+    if (rc != 0) {
+        fprintf(stderr, "%s: WSAStartup() failed: %d\n", __FILE__, rc);
+        fflush(stderr);
+        goto fail;
+    }
+    wsa_inited = 1;
+
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = IPPROTO_TCP;
+    hints.ai_flags = AI_PASSIVE;
+
+    rc = getaddrinfo(DUK_DEBUG_ADDRESS, DUK__STRINGIFY(DUK_DEBUG_PORT), &hints, &result);
+    if (rc != 0) {
+        fprintf(stderr, "%s: getaddrinfo() failed: %d\n", __FILE__, rc);
+        fflush(stderr);
+        goto fail;
+    }
+
+    server_sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
+    if (server_sock == INVALID_SOCKET) {
+        fprintf(stderr, "%s: socket() failed with error: %ld\n",
+                __FILE__, (long) WSAGetLastError());
+        fflush(stderr);
+        goto fail;
+    }
+
+    rc = bind(server_sock, result->ai_addr, (int) result->ai_addrlen);
+    if (rc == SOCKET_ERROR) {
+        fprintf(stderr, "%s: bind() failed with error: %ld\n",
+                __FILE__, (long) WSAGetLastError());
+        fflush(stderr);
+        goto fail;
+    }
+
+    rc = listen(server_sock, SOMAXCONN);
+    if (rc == SOCKET_ERROR) {
+        fprintf(stderr, "%s: listen() failed with error: %ld\n",
+                __FILE__, (long) WSAGetLastError());
+        fflush(stderr);
+        goto fail;
+    }
+
+    if (result != NULL) {
+        freeaddrinfo(result);
+        result = NULL;
+    }
+    return;
+
+ fail:
+    if (result != NULL) {
+        freeaddrinfo(result);
+        result = NULL;
+    }
+    if (server_sock != INVALID_SOCKET) {
+        (void) closesocket(server_sock);
+        server_sock = INVALID_SOCKET;
+    }
+    if (wsa_inited) {
+        WSACleanup();
+        wsa_inited = 0;
+    }
+}
+
+void duk_trans_socket_finish(void) {
+    if (client_sock != INVALID_SOCKET) {
+        (void) closesocket(client_sock);
+        client_sock = INVALID_SOCKET;
+    }
+    if (server_sock != INVALID_SOCKET) {
+        (void) closesocket(server_sock);
+        server_sock = INVALID_SOCKET;
+    }
+    if (wsa_inited) {
+        WSACleanup();
+        wsa_inited = 0;
+    }
+
+    // auto-restart
+    do_reconnect();
+
+}
+
+void duk_trans_socket_waitconn(void) {
+    if (server_sock == INVALID_SOCKET) {
+        fprintf(stderr, "%s: no server socket, skip waiting for connection\n",
+                __FILE__);
+        fflush(stderr);
+        return;
+    }
+    if (client_sock != INVALID_SOCKET) {
+        (void) closesocket(client_sock);
+        client_sock = INVALID_SOCKET;
+    }
+
+    fprintf(stderr, "Waiting for debug connection on port %d\n", (int) DUK_DEBUG_PORT);
+    fflush(stderr);
+
+    client_sock = accept(server_sock, NULL, NULL);
+    if (client_sock == INVALID_SOCKET) {
+        fprintf(stderr, "%s: accept() failed with error %ld, skip waiting for connection\n",
+                __FILE__, (long) WSAGetLastError());
+        fflush(stderr);
+        goto fail;
+    }
+
+    fprintf(stderr, "Debug connection established\n");
+    fflush(stderr);
+
+    /* XXX: For now, close the listen socket because we won't accept new
+     * connections anyway.  A better implementation would allow multiple
+     * debug attaches.
+     */
+
+    if (server_sock != INVALID_SOCKET) {
+        (void) closesocket(server_sock);
+        server_sock = INVALID_SOCKET;
+    }
+    return;
+
+ fail:
+    if (client_sock != INVALID_SOCKET) {
+        (void) closesocket(client_sock);
+        client_sock = INVALID_SOCKET;
+    }
+}
+
+/*
+ *  Duktape callbacks
+ */
+
+/* Duktape debug transport callback: (possibly partial) read. */
+duk_size_t duk_trans_socket_read_cb(void *udata, char *buffer, duk_size_t length) {
+    int ret;
+
+    (void) udata;  /* not needed by the example */
+
+#if defined(DEBUG_PRINTS)
+    fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n",
+            __FUNCTION__, (void *) udata, (void *) buffer, (long) length);
+    fflush(stderr);
+#endif
+
+    if (client_sock == INVALID_SOCKET) {
+        return 0;
+    }
+
+    if (length == 0) {
+        /* This shouldn't happen. */
+        fprintf(stderr, "%s: read request length == 0, closing connection\n",
+                __FILE__);
+        fflush(stderr);
+        goto fail;
+    }
+
+    if (buffer == NULL) {
+        /* This shouldn't happen. */
+        fprintf(stderr, "%s: read request buffer == NULL, closing connection\n",
+                __FILE__);
+        fflush(stderr);
+        goto fail;
+    }
+
+    /* In a production quality implementation there would be a sanity
+     * timeout here to recover from "black hole" disconnects.
+     */
+
+    ret = recv(client_sock, (void *) buffer, (int) length, 0);
+    if (ret < 0) {
+        fprintf(stderr, "%s: debug read failed, error %d, closing connection\n",
+                __FILE__, ret);
+        fflush(stderr);
+        goto fail;
+    } else if (ret == 0) {
+        fprintf(stderr, "%s: debug read failed, ret == 0 (EOF), closing connection\n",
+                __FILE__);
+        fflush(stderr);
+        goto fail;
+    } else if (ret > (int) length) {
+        fprintf(stderr, "%s: debug read failed, ret too large (%ld > %ld), closing connection\n",
+                __FILE__, (long) ret, (long) length);
+        fflush(stderr);
+        goto fail;
+    }
+
+    return (duk_size_t) ret;
+
+ fail:
+    if (client_sock != INVALID_SOCKET) {
+        (void) closesocket(client_sock);
+        client_sock = INVALID_SOCKET;
+    }
+    return 0;
+}
+
+/* Duktape debug transport callback: (possibly partial) write. */
+duk_size_t duk_trans_socket_write_cb(void *udata, const char *buffer, duk_size_t length) {
+    int ret;
+
+    (void) udata;  /* not needed by the example */
+
+#if defined(DEBUG_PRINTS)
+    fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n",
+            __FUNCTION__, (void *) udata, (const void *) buffer, (long) length);
+    fflush(stderr);
+#endif
+
+    if (client_sock == INVALID_SOCKET) {
+        return 0;
+    }
+
+    if (length == 0) {
+        /* This shouldn't happen. */
+        fprintf(stderr, "%s: write request length == 0, closing connection\n",
+                __FILE__);
+        fflush(stderr);
+        goto fail;
+    }
+
+    if (buffer == NULL) {
+        /* This shouldn't happen. */
+        fprintf(stderr, "%s: write request buffer == NULL, closing connection\n",
+                __FILE__);
+        fflush(stderr);
+        goto fail;
+    }
+
+    /* In a production quality implementation there would be a sanity
+     * timeout here to recover from "black hole" disconnects.
+     */
+
+    ret = send(client_sock, (const void *) buffer, (int) length, 0);
+    if (ret <= 0 || ret > (int) length) {
+        fprintf(stderr, "%s: debug write failed, ret %d, closing connection\n",
+                __FILE__, ret);
+        fflush(stderr);
+        goto fail;
+    }
+
+    return (duk_size_t) ret;
+
+ fail:
+    if (client_sock != INVALID_SOCKET) {
+        (void) closesocket(INVALID_SOCKET);
+        client_sock = INVALID_SOCKET;
+    }
+    return 0;
+}
+
+duk_size_t duk_trans_socket_peek_cb(void *udata) {
+    u_long avail;
+    int rc;
+
+    (void) udata;  /* not needed by the example */
+
+#if defined(DEBUG_PRINTS)
+    fprintf(stderr, "%s: udata=%p\n", __FUNCTION__, (void *) udata);
+    fflush(stderr);
+#endif
+
+    if (client_sock == INVALID_SOCKET) {
+        return 0;
+    }
+
+    avail = 0;
+    rc = ioctlsocket(client_sock, FIONREAD, &avail);
+    if (rc != 0) {
+        fprintf(stderr, "%s: ioctlsocket() returned %d, closing connection\n",
+                __FILE__, rc);
+        fflush(stderr);
+        goto fail;  /* also returns 0, which is correct */
+    } else {
+        if (avail == 0) {
+            return 0;  /* nothing to read */
+        } else {
+            return 1;  /* something to read */
+        }
+    }
+    /* never here */
+
+ fail:
+    if (client_sock != INVALID_SOCKET) {
+        (void) closesocket(client_sock);
+        client_sock = INVALID_SOCKET;
+    }
+    return 0;
+}
+
+void duk_trans_socket_read_flush_cb(void *udata) {
+    (void) udata;  /* not needed by the example */
+
+#if defined(DEBUG_PRINTS)
+    fprintf(stderr, "%s: udata=%p\n", __FUNCTION__, (void *) udata);
+    fflush(stderr);
+#endif
+
+    /* Read flush: Duktape may not be making any more read calls at this
+     * time.  If the transport maintains a receive window, it can use a
+     * read flush as a signal to update the window status to the remote
+     * peer.  A read flush is guaranteed to occur before Duktape stops
+     * reading for a while; it may occur in other situations as well so
+     * it's not a 100% reliable indication.
+     */
+
+    /* This TCP transport requires no read flush handling so ignore.
+     * You can also pass a NULL to duk_debugger_attach() and not
+     * implement this callback at all.
+     */
+}
+
+void duk_trans_socket_write_flush_cb(void *udata) {
+    (void) udata;  /* not needed by the example */
+
+#if defined(DEBUG_PRINTS)
+    fprintf(stderr, "%s: udata=%p\n", __FUNCTION__, (void *) udata);
+    fflush(stderr);
+#endif
+
+    /* Write flush.  If the transport combines multiple writes
+     * before actually sending, a write flush is an indication
+     * to write out any pending bytes: Duktape may not be doing
+     * any more writes on this occasion.
+     */
+
+    /* This TCP transport requires no write flush handling so ignore.
+     * You can also pass a NULL to duk_debugger_attach() and not
+     * implement this callback at all.
+     */
+    return;
+}
+
+#undef DUK__STRINGIFY_HELPER
+#undef DUK__STRINGIFY
+
+
+#else  // OSX ad LINUX
+
+    /*
+     *  Example debug transport using a Linux/Unix TCP socket
+     *
+     *  Provides a TCP server socket which a debug client can connect to.
+     *  After that data is just passed through.
+     */
+
+    #include <stdio.h>
+    #include <string.h>
+    #include <sys/socket.h>
+    #include <netinet/in.h>
+    #include <unistd.h>
+    #include <poll.h>
+    #include <errno.h>
+    #include <ThirdParty/Duktape/duktape.h>
+
+    #if !defined(DUK_DEBUG_PORT)
+    #define DUK_DEBUG_PORT 9091
+    #endif
+
+    #if 0
+    #define DEBUG_PRINTS
+    #endif
+
+
+    static int server_sock = -1;
+    static int client_sock = -1;
+
+    /*
+     *  Transport init and finish
+     */
+
+    void duk_trans_socket_init(void) {
+        struct sockaddr_in addr;
+        int on;
+
+        server_sock = socket(AF_INET, SOCK_STREAM, 0);
+        if (server_sock < 0) {
+            fprintf(stderr, "%s: failed to create server socket: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;
+        }
+
+        on = 1;
+        if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on)) < 0) {
+            fprintf(stderr, "%s: failed to set SO_REUSEADDR for server socket: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;
+        }
+
+        memset((void *) &addr, 0, sizeof(addr));
+        addr.sin_family = AF_INET;
+        addr.sin_addr.s_addr = INADDR_ANY;
+        addr.sin_port = htons(DUK_DEBUG_PORT);
+
+        if (bind(server_sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+            fprintf(stderr, "%s: failed to bind server socket: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;
+        }
+
+        listen(server_sock, 1 /*backlog*/);
+        return;
+
+     fail:
+        if (server_sock >= 0) {
+            (void) close(server_sock);
+            server_sock = -1;
+        }
+    }
+
+    void duk_trans_socket_waitconn(void) {
+        struct sockaddr_in addr;
+        socklen_t sz;
+
+        if (server_sock < 0) {
+            fprintf(stderr, "%s: no server socket, skip waiting for connection\n",
+                    __FILE__);
+            fflush(stderr);
+            return;
+        }
+        if (client_sock >= 0) {
+            (void) close(client_sock);
+            client_sock = -1;
+        }
+
+        fprintf(stderr, "Waiting for debug connection on port %d\n", (int) DUK_DEBUG_PORT);
+        fflush(stderr);
+
+        sz = (socklen_t) sizeof(addr);
+        client_sock = accept(server_sock, (struct sockaddr *) &addr, &sz);
+        if (client_sock < 0) {
+            fprintf(stderr, "%s: accept() failed, skip waiting for connection: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;
+        }
+
+        fprintf(stderr, "Debug connection established\n");
+        fflush(stderr);
+
+        /* XXX: For now, close the listen socket because we won't accept new
+         * connections anyway.  A better implementation would allow multiple
+         * debug attaches.
+         */
+
+        if (server_sock >= 0) {
+            (void) close(server_sock);
+            server_sock = -1;
+        }
+        return;
+
+     fail:
+        if (client_sock >= 0) {
+            (void) close(client_sock);
+            client_sock = -1;
+        }
+    }
+
+    void duk_trans_socket_finish(void) {
+        if (client_sock >= 0) {
+            (void) close(client_sock);
+            client_sock = -1;
+        }
+        if (server_sock >= 0) {
+            (void) close(server_sock);
+            server_sock = -1;
+        }
+
+        //
+        // auto-restart
+        do_reconnect();
+    }
+
+
+    /*
+     *  Duktape callbacks
+     */
+
+    /* Duktape debug transport callback: (possibly partial) read. */
+    duk_size_t duk_trans_socket_read_cb(void *udata, char *buffer, duk_size_t length) {
+        ssize_t ret;
+
+        (void) udata;  /* not needed by the example */
+
+    #if defined(DEBUG_PRINTS)
+        fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n",
+                __func__, (void *) udata, (void *) buffer, (long) length);
+        fflush(stderr);
+    #endif
+
+        if (client_sock < 0) {
+            return 0;
+        }
+
+        if (length == 0) {
+            /* This shouldn't happen. */
+            fprintf(stderr, "%s: read request length == 0, closing connection\n",
+                    __FILE__);
+            fflush(stderr);
+            goto fail;
+        }
+
+        if (buffer == NULL) {
+            /* This shouldn't happen. */
+            fprintf(stderr, "%s: read request buffer == NULL, closing connection\n",
+                    __FILE__);
+            fflush(stderr);
+            goto fail;
+        }
+
+        /* In a production quality implementation there would be a sanity
+         * timeout here to recover from "black hole" disconnects.
+         */
+
+        ret = read(client_sock, (void *) buffer, (size_t) length);
+        if (ret < 0) {
+            fprintf(stderr, "%s: debug read failed, closing connection: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;
+        } else if (ret == 0) {
+            fprintf(stderr, "%s: debug read failed, ret == 0 (EOF), closing connection\n",
+                    __FILE__);
+            fflush(stderr);
+            goto fail;
+        } else if (ret > (ssize_t) length) {
+            fprintf(stderr, "%s: debug read failed, ret too large (%ld > %ld), closing connection\n",
+                    __FILE__, (long) ret, (long) length);
+            fflush(stderr);
+            goto fail;
+        }
+
+        return (duk_size_t) ret;
+
+     fail:
+        if (client_sock >= 0) {
+            (void) close(client_sock);
+            client_sock = -1;
+        }
+        return 0;
+    }
+
+    /* Duktape debug transport callback: (possibly partial) write. */
+    duk_size_t duk_trans_socket_write_cb(void *udata, const char *buffer, duk_size_t length) {
+        ssize_t ret;
+
+        (void) udata;  /* not needed by the example */
+
+    #if defined(DEBUG_PRINTS)
+        fprintf(stderr, "%s: udata=%p, buffer=%p, length=%ld\n",
+                __func__, (void *) udata, (const void *) buffer, (long) length);
+        fflush(stderr);
+    #endif
+
+        if (client_sock < 0) {
+            return 0;
+        }
+
+        if (length == 0) {
+            /* This shouldn't happen. */
+            fprintf(stderr, "%s: write request length == 0, closing connection\n",
+                    __FILE__);
+            fflush(stderr);
+            goto fail;
+        }
+
+        if (buffer == NULL) {
+            /* This shouldn't happen. */
+            fprintf(stderr, "%s: write request buffer == NULL, closing connection\n",
+                    __FILE__);
+            fflush(stderr);
+            goto fail;
+        }
+
+        /* In a production quality implementation there would be a sanity
+         * timeout here to recover from "black hole" disconnects.
+         */
+
+        ret = write(client_sock, (const void *) buffer, (size_t) length);
+        if (ret <= 0 || ret > (ssize_t) length) {
+            fprintf(stderr, "%s: debug write failed, closing connection: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;
+        }
+
+        return (duk_size_t) ret;
+
+     fail:
+        if (client_sock >= 0) {
+            (void) close(client_sock);
+            client_sock = -1;
+        }
+        return 0;
+    }
+
+    duk_size_t duk_trans_socket_peek_cb(void *udata) {
+        struct pollfd fds[1];
+        int poll_rc;
+
+        (void) udata;  /* not needed by the example */
+
+    #if defined(DEBUG_PRINTS)
+        fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata);
+        fflush(stderr);
+    #endif
+
+        if (client_sock < 0) {
+            return 0;
+        }
+
+        fds[0].fd = client_sock;
+        fds[0].events = POLLIN;
+        fds[0].revents = 0;
+
+        poll_rc = poll(fds, 1, 0);
+        if (poll_rc < 0) {
+            fprintf(stderr, "%s: poll returned < 0, closing connection: %s\n",
+                    __FILE__, strerror(errno));
+            fflush(stderr);
+            goto fail;  /* also returns 0, which is correct */
+        } else if (poll_rc > 1) {
+            fprintf(stderr, "%s: poll returned > 1, treating like 1\n",
+                    __FILE__);
+            fflush(stderr);
+            return 1;  /* should never happen */
+        } else if (poll_rc == 0) {
+            return 0;  /* nothing to read */
+        } else {
+            return 1;  /* something to read */
+        }
+
+     fail:
+        if (client_sock >= 0) {
+            (void) close(client_sock);
+            client_sock = -1;
+        }
+        return 0;
+    }
+
+    void duk_trans_socket_read_flush_cb(void *udata) {
+        (void) udata;  /* not needed by the example */
+
+    #if defined(DEBUG_PRINTS)
+        fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata);
+        fflush(stderr);
+    #endif
+
+        /* Read flush: Duktape may not be making any more read calls at this
+         * time.  If the transport maintains a receive window, it can use a
+         * read flush as a signal to update the window status to the remote
+         * peer.  A read flush is guaranteed to occur before Duktape stops
+         * reading for a while; it may occur in other situations as well so
+         * it's not a 100% reliable indication.
+         */
+
+        /* This TCP transport requires no read flush handling so ignore.
+         * You can also pass a NULL to duk_debugger_attach() and not
+         * implement this callback at all.
+         */
+    }
+
+    void duk_trans_socket_write_flush_cb(void *udata) {
+        (void) udata;  /* not needed by the example */
+
+    #if defined(DEBUG_PRINTS)
+        fprintf(stderr, "%s: udata=%p\n", __func__, (void *) udata);
+        fflush(stderr);
+    #endif
+
+        /* Write flush.  If the transport combines multiple writes
+         * before actually sending, a write flush is an indication
+         * to write out any pending bytes: Duktape may not be doing
+         * any more writes on this occasion.
+         */
+
+        /* This TCP transport requires no write flush handling so ignore.
+         * You can also pass a NULL to duk_debugger_attach() and not
+         * implement this callback at all.
+         */
+        return;
+    }
+#endif // OSX ad LINUX
+
+    void debugger_detached (void *udata) {
+
+        fflush(stderr);
+
+        /* Ensure socket is closed even when detach is initiated by Duktape
+         * rather than debug client.
+         */
+        duk_trans_socket_finish();
+
+    }
+
+    // hack around oo programming, so a function can call a class method.
+    void set_autoreconn ( IPCPlayerApp *someptr )
+    {
+        arobj = someptr;
+    }
+
     IPCPlayerApp::IPCPlayerApp(Context* context) :
     IPCPlayerApp::IPCPlayerApp(Context* context) :
         PlayerApp(context),
         PlayerApp(context),
         subprocess_(false),
         subprocess_(false),
@@ -171,6 +977,7 @@ namespace Atomic
             SubscribeToEvent(E_SCREENMODE, ATOMIC_HANDLER(IPCPlayerApp, HandlePlayerWindowChanged));
             SubscribeToEvent(E_SCREENMODE, ATOMIC_HANDLER(IPCPlayerApp, HandlePlayerWindowChanged));
             SubscribeToEvent(E_WINDOWPOS, ATOMIC_HANDLER(IPCPlayerApp, HandlePlayerWindowChanged));
             SubscribeToEvent(E_WINDOWPOS, ATOMIC_HANDLER(IPCPlayerApp, HandlePlayerWindowChanged));
             SubscribeToEvent(E_UPDATESPAUSEDRESUMED, ATOMIC_HANDLER(IPCPlayerApp, HandleUpdatesPausedResumed));
             SubscribeToEvent(E_UPDATESPAUSEDRESUMED, ATOMIC_HANDLER(IPCPlayerApp, HandleUpdatesPausedResumed));
+            SubscribeToEvent(E_JSDEBUGGER, ATOMIC_HANDLER(IPCPlayerApp, HandleJSDebugRequest));
 
 
             if (ipc_->InitWorker((unsigned)id, fd_[0], fd_[1]))
             if (ipc_->InitWorker((unsigned)id, fd_[0], fd_[1]))
             {
             {
@@ -192,6 +999,8 @@ namespace Atomic
             }
             }
 
 
             SubscribeToEvent(E_PLAYERQUIT, ATOMIC_HANDLER(IPCPlayerApp, HandleQuit));
             SubscribeToEvent(E_PLAYERQUIT, ATOMIC_HANDLER(IPCPlayerApp, HandleQuit));
+
+            set_autoreconn (this);  // for auto-reconnect
         }
         }
 
 
         GetSubsystem<Graphics>()->RaiseWindow();
         GetSubsystem<Graphics>()->RaiseWindow();
@@ -199,6 +1008,30 @@ namespace Atomic
 
 
     }
     }
 
 
+    void IPCPlayerApp::Reconnect()
+    {
+        duk_trans_socket_init();
+        duk_trans_socket_waitconn();
+
+        JSVM* vm = JSVM::GetJSVM(0);
+        if(!vm) return;
+        duk_context *ctx_ = vm->GetJSContext();
+        if(!ctx_) return;
+        duk_debugger_attach(ctx_,
+            duk_trans_socket_read_cb,
+            duk_trans_socket_write_cb,
+            duk_trans_socket_peek_cb,
+            duk_trans_socket_read_flush_cb,
+            duk_trans_socket_write_flush_cb,
+            debugger_detached,
+            NULL);
+    }
+
+    void IPCPlayerApp::HandleJSDebugRequest(StringHash eventType, VariantMap& eventData)
+    {
+        Reconnect();  // start up the js debugger
+    }
+
     void IPCPlayerApp::HandleQuit(StringHash eventType, VariantMap& eventData)
     void IPCPlayerApp::HandleQuit(StringHash eventType, VariantMap& eventData)
     {
     {
         engine_->Exit();
         engine_->Exit();

+ 3 - 0
Source/AtomicApp/Player/IPCPlayerApp.h

@@ -47,6 +47,8 @@ namespace Atomic
 
 
         virtual void ProcessArguments();
         virtual void ProcessArguments();
 
 
+        void Reconnect();
+
     protected:
     protected:
 
 
     private:
     private:
@@ -59,6 +61,7 @@ namespace Atomic
         void HandleUpdatesPausedResumed(StringHash eventType, VariantMap& eventData);
         void HandleUpdatesPausedResumed(StringHash eventType, VariantMap& eventData);
         void HandleQuit(StringHash eventType, VariantMap& eventData);
         void HandleQuit(StringHash eventType, VariantMap& eventData);
         void HandleExitRequest(StringHash eventType, VariantMap& eventData);
         void HandleExitRequest(StringHash eventType, VariantMap& eventData);
+        void HandleJSDebugRequest(StringHash eventType, VariantMap& eventData);
 
 
         /// true if we're launched as a subprocess, otherwise top level process (generally for debugging)
         /// true if we're launched as a subprocess, otherwise top level process (generally for debugging)
         bool subprocess_;
         bool subprocess_;

+ 10 - 3
Source/AtomicEditor/EditorMode/AEEditorMode.cpp

@@ -204,7 +204,7 @@ bool EditorMode::PlayProjectInternal(const String &addArgs, bool debug)
 
 
 #ifdef ATOMIC_DEV_BUILD
 #ifdef ATOMIC_DEV_BUILD
 
 
-#ifdef ATOMIC_DEBUG        
+#ifdef ATOMIC_DEBUG
         playerBinary = project->GetProjectPath() + "AtomicNET/Debug/Bin/Desktop/" + projectExe;
         playerBinary = project->GetProjectPath() + "AtomicNET/Debug/Bin/Desktop/" + projectExe;
 #else
 #else
         playerBinary = project->GetProjectPath() + "AtomicNET/Release/Bin/Desktop/" + projectExe;
         playerBinary = project->GetProjectPath() + "AtomicNET/Release/Bin/Desktop/" + projectExe;
@@ -215,7 +215,7 @@ bool EditorMode::PlayProjectInternal(const String &addArgs, bool debug)
         playerBinary = project->GetProjectPath() + "AtomicNET/Release/Bin/Desktop/" + projectExe;
         playerBinary = project->GetProjectPath() + "AtomicNET/Release/Bin/Desktop/" + projectExe;
 #endif
 #endif
 
 
-        
+
         if (!fileSystem->FileExists(playerBinary))
         if (!fileSystem->FileExists(playerBinary))
         {
         {
             ATOMIC_LOGERRORF("Managed player: %s does not exist", playerBinary.CString());
             ATOMIC_LOGERRORF("Managed player: %s does not exist", playerBinary.CString());
@@ -313,7 +313,7 @@ bool EditorMode::PlayProjectInternal(const String &addArgs, bool debug)
         SubscribeToEvent(E_IPCPLAYERUPDATESPAUSEDRESUMED, ATOMIC_HANDLER(EditorMode, HandleIPCPlayerUpdatesPausedResumed));
         SubscribeToEvent(E_IPCPLAYERUPDATESPAUSEDRESUMED, ATOMIC_HANDLER(EditorMode, HandleIPCPlayerUpdatesPausedResumed));
         SubscribeToEvent(E_IPCPLAYERPAUSESTEPREQUEST, ATOMIC_HANDLER(EditorMode, HandleIPCPlayerPauseStepRequest));
         SubscribeToEvent(E_IPCPLAYERPAUSESTEPREQUEST, ATOMIC_HANDLER(EditorMode, HandleIPCPlayerPauseStepRequest));
         SubscribeToEvent(E_IPCPLAYEREXITREQUEST, ATOMIC_HANDLER(EditorMode, HandleIPCPlayerExitRequest));
         SubscribeToEvent(E_IPCPLAYEREXITREQUEST, ATOMIC_HANDLER(EditorMode, HandleIPCPlayerExitRequest));
-    
+
 
 
         SubscribeToEvent(playerBroker_, E_IPCJSERROR, ATOMIC_HANDLER(EditorMode, HandleIPCJSError));
         SubscribeToEvent(playerBroker_, E_IPCJSERROR, ATOMIC_HANDLER(EditorMode, HandleIPCJSError));
         SubscribeToEvent(playerBroker_, E_IPCWORKEREXIT, ATOMIC_HANDLER(EditorMode, HandleIPCWorkerExit));
         SubscribeToEvent(playerBroker_, E_IPCWORKEREXIT, ATOMIC_HANDLER(EditorMode, HandleIPCWorkerExit));
@@ -361,4 +361,11 @@ bool EditorMode::IsPlayerEnabled()
     return playerEnabled_;
     return playerEnabled_;
 }
 }
 
 
+void EditorMode::PlayerJSDebug()
+{
+    if (!playerBroker_) return;
+    VariantMap noEventData;
+    playerBroker_->PostMessage( E_JSDEBUGGER, noEventData);
+}
+
 }
 }

+ 1 - 0
Source/AtomicEditor/EditorMode/AEEditorMode.h

@@ -49,6 +49,7 @@ public:
 
 
     bool PlayProject(String addArgs = "", bool debug = false);
     bool PlayProject(String addArgs = "", bool debug = false);
     bool IsPlayerEnabled();
     bool IsPlayerEnabled();
+    void PlayerJSDebug();
 
 
 private:
 private: