Skip to content

Commit

Permalink
Updated to use class-based debugger interface, and removed useless code
Browse files Browse the repository at this point in the history
  • Loading branch information
pcutillas committed Feb 10, 2021
1 parent faf1d3d commit cc9f723
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 121 deletions.
75 changes: 13 additions & 62 deletions adapter/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,25 @@
"""

from interface import DebuggerInterface
from tempfile import gettempdir
from interface import debugger_queue, start_response_thread, read_debugger_input
from queue import Queue
from util import *
import winapi
import socket
import json
import time
import sys
import os

interface = None

# The 3DS Max window handle
window = None

signal_location = join(dirname(abspath(__file__)), 'finished.txt')
last_seq = -1

processed_seqs = []
run_code = ""
last_seq = -1

ptvsd_send_queue = Queue()
ptvsd_socket: socket.socket
ptvsd_socket = None


# Avoiding stalls
Expand All @@ -51,14 +47,12 @@ def main():
then remains in a loop reading messages from debugger.
"""

find_max_window()

if os.path.exists(signal_location):
os.remove(signal_location)
global interface

start_response_thread()
find_max_window()

read_debugger_input(on_receive_from_debugger)
interface = DebuggerInterface(on_receive=on_receive_from_debugger)
interface.start()


def on_receive_from_debugger(message):
Expand All @@ -78,7 +72,7 @@ def on_receive_from_debugger(message):

if cmd == 'initialize':
# Run init request once max connection is established and send success response to the debugger
debugger_queue.put(json.dumps(json.loads(INITIALIZE_RESPONSE))) # load and dump to remove indents
interface.send(json.dumps(json.loads(INITIALIZE_RESPONSE))) # load and dump to remove indents
processed_seqs.append(contents['seq'])
pass

Expand Down Expand Up @@ -204,8 +198,7 @@ def attach_to_max(contents: dict):

run_code = RUN_TEMPLATE.format(
dir=dirname(config['program']),
file_name=split(config['program'])[1][:-3] or basename(split(config['program'])[0])[:-3],
signal_location=signal_location.replace('\\', '\\\\')
file_name=split(config['program'])[1][:-3] or basename(split(config['program'])[0])[:-3]
)

# then send attach code
Expand All @@ -217,26 +210,6 @@ def attach_to_max(contents: dict):
# Then start the max debugging threads
run(start_debugging, ((config['ptvsd']['host'], int(config['ptvsd']['port'])),))

# And finally wait for the signal from ptvsd that debugging is done
run(wait_for_signal)


def wait_for_signal():
"""
Waits for the signal location to exist, which means debugging is done.
Deletes the signal location and prepares this adapter for disconnect.
"""

global disconnecting

while True:

if os.path.exists(signal_location):
log('--- FINISHED DEBUGGING ---')

os.remove(signal_location)
run(disconnect)


def start_debugging(address):
"""
Expand Down Expand Up @@ -369,10 +342,10 @@ def on_receive_from_ptvsd(message):

if stashed_event:
log('Received from ptvsd:', message)
debugger_queue.put(message)
interface.send(message)

log('Sending stashed message:', stashed_event)
debugger_queue.put(stashed_event)
interface.send(stashed_event)

stashed_event = None
return
Expand All @@ -383,29 +356,7 @@ def on_receive_from_ptvsd(message):
log("Already processed, ptvsd response is:", message)
else:
log('Received from ptvsd:', message)
debugger_queue.put(message)


def disconnect():
"""
Clean things up by unblocking (and killing) all threads, then exit
"""

# Unblock and kill the send threads
debugger_queue.put(None)
while debugger_queue.qsize() != 0:
time.sleep(0.1)

ptvsd_send_queue.put(None)
while ptvsd_send_queue.qsize() != 0:
time.sleep(0.1)

# Close ptvsd socket and stdin so readline() functions unblock
ptvsd_socket.close()
sys.stdin.close()

# exit all threads
os._exit(0)
interface.send(message)


if __name__ == '__main__':
Expand Down
131 changes: 75 additions & 56 deletions adapter/interface.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,90 @@

from sys import stdin, stdout
from queue import Queue
from util import CONTENT_HEADER, run_in_new_thread, log
from util import CONTENT_HEADER, run, log


debugger_queue = Queue()


def read_debugger_input(callback: function):
"""
Reads DAP messages sent from the debugger through stdin and calls the
function passed in as the callback with the message recieved.
"""

while True:
try:
content_length = 0
while True:
header = stdin.readline()
if header:
header = header.strip()
if not header:
break
if header.startswith(CONTENT_HEADER):
content_length = int(header[len(CONTENT_HEADER):])

if content_length > 0:
total_content = ""
while content_length > 0:
content = stdin.read(content_length)
content_length -= len(content)
total_content += content

if content_length == 0:
message = total_content
callback(message)

except Exception as e:
log("Failure reading stdin: " + str(e))
return


def _debugger_send_loop():
class DebuggerInterface:
"""
Waits for items to show in the send queue and prints them.
Blocks until an item is present
Provides a simple interface to capture and send
messages from/to the debugger vis stdin/stdout.
"""

while True:
msg: str = debugger_queue.get()
if msg is None:
return
else:
def __init__(self, on_receive = None):
self.send_queue = Queue()
self.running = False
self.callback = on_receive

def start(self):
if not self.running:
self.running = True
run(self._debugger_send_loop)
self._read_debugger_input()

def start_nonblocking(self):
if not self.running:
self.running = True
run(self._debugger_send_loop)
run(self._read_debugger_input)

def stop(self):
if self.running:
self.running = False

def send(self, message: str):
self.send_queue.put(message)

def _read_debugger_input(self):
"""
Reads DAP messages sent from the debugger through stdin and calls the
function passed in as the callback with the message recieved.
"""

while self.running:
try:
stdout.write('Content-Length: {}\r\n\r\n'.format(len(msg)))
stdout.write(msg)
stdout.flush()
log('Sent to Debugger:', msg)
content_length = 0
while self.running:
header = stdin.readline()
if header:
header = header.strip()
if not header:
break
if header.startswith(CONTENT_HEADER):
content_length = int(header[len(CONTENT_HEADER):])

if content_length > 0:
total_content = ""
while content_length > 0:
content = stdin.read(content_length)
content_length -= len(content)
total_content += content

if content_length == 0:
message = total_content
if self.callback:
self.callback(message)

except Exception as e:
log("Failure writing to stdout (normal on exit):" + str(e))
log("Failure reading stdin: " + str(e))
return


def start_response_thread():
"""
Simple wrapper to start the debugger send loop below in a new thread
"""
def _debugger_send_loop(self):
"""
Waits for items to show in the send queue and prints them.
Blocks until an item is present
"""

run_in_new_thread(_debugger_send_loop)
while self.running:
msg: str = self.send_queue.get()
if msg is None:
return
else:
try:
stdout.write('Content-Length: {}\r\n\r\n'.format(len(msg)))
stdout.write(msg)
stdout.flush()
log('Sent to Debugger:', msg)
except Exception as e:
log("Failure writing to stdout (normal on exit):" + str(e))

2 changes: 0 additions & 2 deletions adapter/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ def run(func, args=None, time=0.01):
reload({file_name})
print(' --- Finished debugging {file_name} --- \\n')
open("{signal_location}", "w").close() # Create this file to let the adapter know debugging is finished
except Exception as e:
print('Error while debugging: ' + str(e))
Expand Down
2 changes: 1 addition & 1 deletion attach.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import time


adapter_type = "3DSMPy2" # NOTE: type name must be unique to each adapter
adapter_type = "3dsMax" # NOTE: type name must be unique to each adapter
package_path = dirname(abspath(__file__))
adapter_path = join(package_path, "adapter")

Expand Down

0 comments on commit cc9f723

Please sign in to comment.