Skip to content

Commit 8fb9539

Browse files
committed
Move hxd.net.RemoteConsole to hide
see #263
1 parent 1d7d04d commit 8fb9539

File tree

2 files changed

+286
-5
lines changed

2 files changed

+286
-5
lines changed

hide/view/RemoteConsoleView.hx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class RemoteConsolePanel extends hide.comp.Component {
113113
var statusBarIcon : Element;
114114
var statusIcon : Element;
115115
var handler : RemoteCommandHandler;
116-
var rcmd : hxd.net.RemoteConsole;
116+
var rcmd : hrt.impl.RemoteConsole;
117117
public function new( view : RemoteConsoleView, host : String, port : Int, commands : Array<String> ) {
118118
super(null, null);
119119
this.view = view;
@@ -150,7 +150,7 @@ class RemoteConsolePanel extends hide.comp.Component {
150150
});
151151

152152
var connectHost = element.find("#connectHost");
153-
connectHost.val(host ?? hxd.net.RemoteConsole.DEFAULT_HOST);
153+
connectHost.val(host ?? hrt.impl.RemoteConsole.DEFAULT_HOST);
154154
function checkHost() {
155155
var host = connectHost.val();
156156
js.node.Dns.lookup(host, function(err, address, family) {
@@ -170,11 +170,11 @@ class RemoteConsolePanel extends hide.comp.Component {
170170
});
171171

172172
var connectPort = element.find("#connectPort");
173-
connectPort.val(port ?? hxd.net.RemoteConsole.DEFAULT_PORT);
173+
connectPort.val(port ?? hrt.impl.RemoteConsole.DEFAULT_PORT);
174174
function checkPort() {
175175
var port = Std.int(connectPort.val());
176176
if( port < 0 )
177-
port = isConnected() ? rcmd.port : hxd.net.RemoteConsole.DEFAULT_PORT;
177+
port = isConnected() ? rcmd.port : hrt.impl.RemoteConsole.DEFAULT_PORT;
178178
connectPort.val(port);
179179
}
180180
connectPort.keydown(function(e) {
@@ -188,7 +188,7 @@ class RemoteConsolePanel extends hide.comp.Component {
188188
close();
189189
var host = element.find("#connectHost").val();
190190
var port = Std.parseInt(element.find("#connectPort").val());
191-
rcmd = new hxd.net.RemoteConsole(port, host);
191+
rcmd = new hrt.impl.RemoteConsole(port, host);
192192
rcmd.log = (msg) -> log(msg);
193193
rcmd.logError = (msg) -> log(msg, true);
194194
rcmd.registerCommands(handler);

hrt/impl/RemoteConsole.hx

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package hrt.impl;
2+
3+
/**
4+
A simple socket-based local communication channel,
5+
aim at communicate between 2 programs (e.g. Hide and a HL game).
6+
7+
Usage:
8+
```haxe
9+
var rcmd = new hrt.impl.RemoteConsole();
10+
// rcmd.log = (msg) -> logToUI(msg);
11+
// rcmd.logError = (msg) -> logErrorToUI(msg);
12+
rcmd.registerCommands(handler);
13+
rcmd.connect(); // or rcmd.startServer()
14+
rc.sendCommand("log", "Hello!", function(r) {});
15+
```
16+
*/
17+
@:keep
18+
@:rtti
19+
class RemoteConsole {
20+
public static var DEFAULT_HOST : String = "127.0.0.1";
21+
public static var DEFAULT_PORT : Int = 40001;
22+
23+
var UID : Int = 0;
24+
public var host : String;
25+
public var port : Int;
26+
var sock : hxd.net.Socket;
27+
var cSocks : Array<hxd.net.Socket>;
28+
var waitReply : Map<Int, Dynamic->Void>;
29+
30+
public function new( ?port : Int, ?host : String ) {
31+
this.host = host ?? DEFAULT_HOST;
32+
this.port = port ?? DEFAULT_PORT;
33+
registerCommands(this);
34+
}
35+
36+
public function startServer() {
37+
close();
38+
sock = new hxd.net.Socket();
39+
sock.onError = function(msg) {
40+
logError("Socket Error: " + msg);
41+
close();
42+
}
43+
cSocks = [];
44+
sock.bind(host, port, function(s) {
45+
cSocks.push(s);
46+
s.onError = function(msg) {
47+
logError("Client error: " + msg);
48+
if( s != null )
49+
s.close();
50+
if( s != null && cSocks != null ) {
51+
cSocks.remove(s);
52+
}
53+
}
54+
s.onData = () -> handleOnData(s);
55+
log("Client connected");
56+
}, 1);
57+
log('Server started at $host:$port');
58+
}
59+
60+
public function connect( ?onConnected : Bool -> Void ) {
61+
close();
62+
sock = new hxd.net.Socket();
63+
sock.onError = function(msg) {
64+
logError("Socket Error: " + msg);
65+
close();
66+
if( onConnected != null )
67+
onConnected(false);
68+
}
69+
sock.onData = () -> handleOnData(sock);
70+
sock.connect(host, port, function() {
71+
log("Connected to server");
72+
if( onConnected != null )
73+
onConnected(true);
74+
});
75+
log('Connecting to $host:$port');
76+
}
77+
78+
public function close() {
79+
if( sock != null ) {
80+
sock.close();
81+
sock = null;
82+
}
83+
if( cSocks != null ) {
84+
for( s in cSocks )
85+
s.close();
86+
cSocks = null;
87+
}
88+
UID = 0;
89+
waitReply = [];
90+
}
91+
92+
public function isConnected() {
93+
return sock != null;
94+
}
95+
96+
public dynamic function log( msg : String ) {
97+
trace(msg);
98+
}
99+
100+
public dynamic function logError( msg : String ) {
101+
trace('[Error] $msg');
102+
}
103+
104+
public function sendCommand( cmd : String, ?args : Dynamic, ?onResult : Dynamic -> Void ) {
105+
var id = ++UID;
106+
waitReply.set(id, onResult);
107+
sendData(cmd, args, id);
108+
}
109+
110+
function sendData( cmd : String, args : Dynamic, id : Int ) {
111+
var obj = { cmd : cmd, args : args, id : id};
112+
var bytes = haxe.io.Bytes.ofString(haxe.Json.stringify(obj) + "\n");
113+
if( cSocks != null ) {
114+
for( cs in cSocks ) {
115+
cs.out.writeBytes(bytes, 0, bytes.length);
116+
}
117+
} else {
118+
sock.out.writeBytes(bytes, 0, bytes.length);
119+
}
120+
}
121+
122+
function handleOnData( s : hxd.net.Socket ) {
123+
var str = s.input.readLine().toString();
124+
var obj = try { haxe.Json.parse(str); } catch (e) { logError("Parse error: " + e); null; };
125+
if( obj == null || obj.id == null ) {
126+
return;
127+
}
128+
var id : Int = obj.id;
129+
if( id <= 0 ) {
130+
var onResult = waitReply.get(-id);
131+
waitReply.remove(-id);
132+
if( onResult != null ) {
133+
onResult(obj.args);
134+
}
135+
} else {
136+
onCommand(obj.cmd, obj.args, (result) -> sendData(null, result, -id));
137+
}
138+
}
139+
140+
function onCommand( cmd : String, args : Dynamic, onDone : Dynamic -> Void ) : Void {
141+
if( cmd == null )
142+
return;
143+
var command = commands.get(cmd);
144+
if( command == null ) {
145+
logError("Unsupported command " + cmd);
146+
return;
147+
}
148+
command(args, onDone);
149+
}
150+
151+
// ----- Commands -----
152+
153+
var commands = new Map<String, (args:Dynamic, onDone:Dynamic->Void) -> Void>();
154+
155+
/**
156+
register a single command f.
157+
`args` can be null, or an object that can be parse from/to json.
158+
`onDone(result)` must be call when `f` finished, and `result` can be null or a json serializable object.
159+
*/
160+
public function registerCommand( name : String, f : (args:Dynamic, onDone:Dynamic->Void) -> Void ) {
161+
commands.set(name, f);
162+
}
163+
164+
/**
165+
Register functions marked with `@cmd` in instance `o` as command handler (class of `o` needs `@:rtti` and `@:keep`).
166+
This is done with `Reflect` and `registerCommand`, `onDone` call are inserted automatically when necessary.
167+
Function name will be used as `cmd` key (and alias name if `@cmd("aliasname")`),
168+
if multiple function use the same name, only the latest registered is taken into account.
169+
170+
Supported `@cmd` function signature:
171+
``` haxe
172+
@cmd function foo() : Dynamic {}
173+
@cmd function foo(args : Dynamic) : Dynamic {}
174+
@cmd function foo(onDone : Dynamic->Void) : Void {}
175+
@cmd function foo(args : Dynamic, onDone : Dynamic->Void) : Void {}
176+
```
177+
*/
178+
public function registerCommands( o : Dynamic ) {
179+
function regRec( cl : Dynamic ) {
180+
if( !haxe.rtti.Rtti.hasRtti(cl) )
181+
return;
182+
var rtti = haxe.rtti.Rtti.getRtti(cl);
183+
for( field in rtti.fields ) {
184+
var cmd = null;
185+
for( m in field.meta ) {
186+
if( m.name == "cmd" ) {
187+
cmd = m;
188+
break;
189+
}
190+
}
191+
if( cmd != null ) {
192+
switch( field.type ) {
193+
case CFunction(args, ret):
194+
var name = field.name;
195+
var func = Reflect.field(o, field.name);
196+
var f = null;
197+
if( args.length == 0 ) {
198+
f = (args, onDone) -> onDone(Reflect.callMethod(o, func, []));
199+
} else if( args.length == 1 && args[0].t.match(CFunction(_,_))) {
200+
f = (args, onDone) -> Reflect.callMethod(o, func, [onDone]);
201+
} else if( args.length == 1 ) {
202+
f = (args, onDone) -> onDone(Reflect.callMethod(o, func, [args]));
203+
} else if( args.length == 2 && args[1].t.match(CFunction(_,_)) ) {
204+
f = (args, onDone) -> Reflect.callMethod(o, func, [args, onDone]);
205+
} else {
206+
logError("Invalid @cmd, found: " + args);
207+
continue;
208+
}
209+
registerCommand(name, f);
210+
if( cmd.params.length == 1 ) {
211+
var alias = StringTools.trim(StringTools.replace(cmd.params[0], "\"", ""));
212+
registerCommand(alias, f);
213+
}
214+
default:
215+
}
216+
}
217+
}
218+
}
219+
var cl = Type.getClass(o);
220+
while( cl != null ) {
221+
regRec(cl);
222+
cl = Type.getSuperClass(cl);
223+
}
224+
}
225+
226+
@cmd("log") function logCmd( args : Dynamic ) {
227+
log("[>] " + args);
228+
}
229+
230+
@cmd function cwd() {
231+
return Sys.getCwd();
232+
}
233+
234+
@cmd function programPath() {
235+
return Sys.programPath();
236+
}
237+
238+
#if hl
239+
@cmd function dump( args : { file : String } ) {
240+
hl.Gc.major();
241+
hl.Gc.dumpMemory(args?.file);
242+
if( hxd.res.Resource.LIVE_UPDATE ) {
243+
var msg = "hxd.res.Resource.LIVE_UPDATE is on, you may want to disable it for mem dumps; RemoteConsole can also impact memdumps.";
244+
logError(msg);
245+
sendCommand("log", msg);
246+
}
247+
}
248+
249+
@cmd function prof( args : { action : String, samples : Int, delay_ms : Int }, onDone : Dynamic -> Void ) {
250+
function doProf( args ) {
251+
switch( args.action ) {
252+
case "start":
253+
hl.Profile.event(-7, "" + (args.samples > 0 ? args.samples : 10000)); // setup
254+
hl.Profile.event(-3); // clear data
255+
hl.Profile.event(-5); // resume all
256+
case "resume":
257+
hl.Profile.event(-5); // resume all
258+
case "pause":
259+
hl.Profile.event(-4); // pause all
260+
case "dump":
261+
hl.Profile.event(-6); // save dump
262+
hl.Profile.event(-4); // pause all
263+
hl.Profile.event(-3); // clear data
264+
default:
265+
sendCommand("log", "Missing argument action for prof");
266+
}
267+
}
268+
if( args == null ) {
269+
onDone(null);
270+
} else if( args.delay_ms > 0 ) {
271+
haxe.Timer.delay(function() {
272+
doProf(args);
273+
onDone(null);
274+
}, args.delay_ms);
275+
} else {
276+
doProf(args);
277+
onDone(null);
278+
}
279+
}
280+
#end
281+
}

0 commit comments

Comments
 (0)