forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
10
Kha/Sources/kha/netsync/Client.hx
Normal file
10
Kha/Sources/kha/netsync/Client.hx
Normal file
@ -0,0 +1,10 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
interface Client {
|
||||
var id(get, null): Int;
|
||||
function send(bytes: Bytes, mandatory: Bool): Void;
|
||||
function receive(receiver: Bytes->Void): Void;
|
||||
function onClose(close: Void->Void): Void;
|
||||
}
|
22
Kha/Sources/kha/netsync/Controller.hx
Normal file
22
Kha/Sources/kha/netsync/Controller.hx
Normal file
@ -0,0 +1,22 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
@:autoBuild(kha.netsync.ControllerBuilder.build())
|
||||
class Controller {
|
||||
var __id: Int;
|
||||
|
||||
public var _inputBufferIndex: Int;
|
||||
public var _inputBuffer: Bytes;
|
||||
|
||||
public function new() {
|
||||
__id = ControllerBuilder.nextId++;
|
||||
_inputBuffer = Bytes.alloc(1);
|
||||
}
|
||||
|
||||
public function _id(): Int {
|
||||
return __id;
|
||||
}
|
||||
|
||||
public function _receive(bytes: Bytes): Void {}
|
||||
}
|
287
Kha/Sources/kha/netsync/ControllerBuilder.hx
Normal file
287
Kha/Sources/kha/netsync/ControllerBuilder.hx
Normal file
@ -0,0 +1,287 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr.Field;
|
||||
|
||||
class ControllerBuilder {
|
||||
public static var nextId: Int = 0;
|
||||
|
||||
macro static public function build(): Array<Field> {
|
||||
var fields = Context.getBuildFields();
|
||||
|
||||
#if (!kha_server && (kha_html5 || kha_kore))
|
||||
{
|
||||
var funcindex = 0;
|
||||
for (field in fields) {
|
||||
var input = false;
|
||||
for (meta in field.meta) {
|
||||
if (meta.name == "input") {
|
||||
input = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!input)
|
||||
continue;
|
||||
|
||||
switch (field.kind) {
|
||||
case FFun(f):
|
||||
var size = 4;
|
||||
for (arg in f.args) {
|
||||
switch (arg.type) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "Int":
|
||||
size += 4;
|
||||
case "String":
|
||||
size += 1;
|
||||
case "Float":
|
||||
size += 8;
|
||||
case "Bool":
|
||||
size += 1;
|
||||
case "KeyCode":
|
||||
size += 1;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
var expr = macro @:mergeBlock {
|
||||
var bytes = haxe.io.Bytes.alloc($v{size});
|
||||
bytes.setInt32(0, $v{funcindex});
|
||||
};
|
||||
var index: Int = 4;
|
||||
for (arg in f.args) {
|
||||
switch (arg.type) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "Int":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.setInt32($v{index}, $i{argname});
|
||||
};
|
||||
index += 4;
|
||||
case "String":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set($v{index}, $i{argname}.charCodeAt(0));
|
||||
};
|
||||
index += 1;
|
||||
case "Float":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.setDouble($v{index}, $i{argname});
|
||||
};
|
||||
index += 8;
|
||||
case "Bool":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set($v{index}, $i{argname} ?1:0);
|
||||
};
|
||||
index += 1;
|
||||
case "KeyCode":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set($v{index}, $i{argname});
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
var original = f.expr;
|
||||
expr = macro {
|
||||
if (kha.netsync.Session.the() != null) {
|
||||
$expr;
|
||||
kha.netsync.Session.the().sendControllerUpdate(_id(), bytes);
|
||||
}
|
||||
$original;
|
||||
};
|
||||
f.expr = expr;
|
||||
default:
|
||||
}
|
||||
++funcindex;
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
// macros failing everywhere but in JavaScript?
|
||||
#if (kha_server || kha_html5 || kha_kore)
|
||||
var receive = macro @:mergeBlock {
|
||||
var funcindex = bytes.getInt32(0);
|
||||
};
|
||||
{
|
||||
var funcindex = 0;
|
||||
for (field in fields) {
|
||||
var input = false;
|
||||
for (meta in field.meta) {
|
||||
if (meta.name == "input") {
|
||||
input = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!input)
|
||||
continue;
|
||||
|
||||
switch (field.kind) {
|
||||
case FFun(f):
|
||||
var expr = macro {};
|
||||
var index: Int = 4;
|
||||
var varindex: Int = 0;
|
||||
for (arg in f.args) {
|
||||
switch (arg.type) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "Int":
|
||||
var argname = arg.name;
|
||||
var varname = "input" + varindex;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
var $varname:Int = bytes.getInt32($v{index});
|
||||
};
|
||||
index += 4;
|
||||
case "String":
|
||||
var argname = arg.name;
|
||||
var varname = "input" + varindex;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
var $varname:String = String.fromCharCode(bytes.get($v{index}));
|
||||
};
|
||||
index += 1;
|
||||
case "Float":
|
||||
var argname = arg.name;
|
||||
var varname = "input" + varindex;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
var $varname:Float = bytes.getDouble($v{index});
|
||||
};
|
||||
index += 8;
|
||||
case "Bool":
|
||||
var argname = arg.name;
|
||||
var varname = "input" + varindex;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
var $varname:Bool = bytes.get($v{index}) != 0;
|
||||
};
|
||||
index += 1;
|
||||
case "KeyCode":
|
||||
var argname = arg.name;
|
||||
var varname = "input" + varindex;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
var $varname:kha.input.KeyCode = cast bytes.get($v{index});
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
default:
|
||||
}
|
||||
++varindex;
|
||||
}
|
||||
switch (varindex) {
|
||||
case 1:
|
||||
var funcname = field.name;
|
||||
receive = macro @:mergeBlock {
|
||||
$receive;
|
||||
if (funcindex == $v{funcindex}) {
|
||||
$expr;
|
||||
$i{funcname}(input0);
|
||||
return;
|
||||
}
|
||||
};
|
||||
case 2:
|
||||
var funcname = field.name;
|
||||
receive = macro @:mergeBlock {
|
||||
$receive;
|
||||
if (funcindex == $v{funcindex}) {
|
||||
$expr;
|
||||
$i{funcname}(input0, input1);
|
||||
return;
|
||||
}
|
||||
};
|
||||
case 3:
|
||||
var funcname = field.name;
|
||||
receive = macro @:mergeBlock {
|
||||
$receive;
|
||||
if (funcindex == $v{funcindex}) {
|
||||
$expr;
|
||||
$i{funcname}(input0, input1, input2);
|
||||
return;
|
||||
}
|
||||
};
|
||||
case 4:
|
||||
var funcname = field.name;
|
||||
receive = macro @:mergeBlock {
|
||||
$receive;
|
||||
if (funcindex == $v{funcindex}) {
|
||||
$expr;
|
||||
$i{funcname}(input0, input1, input2, input3);
|
||||
return;
|
||||
}
|
||||
};
|
||||
case 5:
|
||||
var funcname = field.name;
|
||||
receive = macro @:mergeBlock {
|
||||
$receive;
|
||||
if (funcindex == $v{funcindex}) {
|
||||
$expr;
|
||||
$i{funcname}(input0, input1, input2, input3, input4);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
default:
|
||||
}
|
||||
++funcindex;
|
||||
}
|
||||
}
|
||||
|
||||
fields.push({
|
||||
name: "_receive",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
params: null,
|
||||
expr: receive,
|
||||
args: [
|
||||
{
|
||||
value: null,
|
||||
type: Context.toComplexType(Context.getType("haxe.io.Bytes")),
|
||||
opt: null,
|
||||
name: "bytes"
|
||||
}
|
||||
]
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#else
|
||||
fields.push({
|
||||
name: "_receive",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: null,
|
||||
params: null,
|
||||
expr: macro {},
|
||||
args: [
|
||||
{
|
||||
value: null,
|
||||
type: Context.toComplexType(Context.getType("haxe.io.Bytes")),
|
||||
opt: null,
|
||||
name: "bytes"
|
||||
}
|
||||
]
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
#end
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
11
Kha/Sources/kha/netsync/Entity.hx
Normal file
11
Kha/Sources/kha/netsync/Entity.hx
Normal file
@ -0,0 +1,11 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
@:autoBuild(kha.netsync.EntityBuilder.build())
|
||||
interface Entity {
|
||||
function _id(): Int;
|
||||
function _size(): Int;
|
||||
function _send(offset: Int, bytes: Bytes): Int;
|
||||
function _receive(offset: Int, bytes: Bytes): Int;
|
||||
}
|
206
Kha/Sources/kha/netsync/EntityBuilder.hx
Normal file
206
Kha/Sources/kha/netsync/EntityBuilder.hx
Normal file
@ -0,0 +1,206 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
class EntityBuilder {
|
||||
public static var nextId: Int = 0;
|
||||
|
||||
macro static public function build(): Array<Field> {
|
||||
var fields = Context.getBuildFields();
|
||||
|
||||
var isBaseEntity = false;
|
||||
for (i in Context.getLocalClass().get().interfaces) {
|
||||
var intf = i.t.get();
|
||||
if (intf.module == "kha.netsync.Entity") {
|
||||
isBaseEntity = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var receive = macro {};
|
||||
|
||||
var send = macro {};
|
||||
|
||||
if (!isBaseEntity) {
|
||||
receive = macro {
|
||||
offset += super._receive(offset, bytes);
|
||||
};
|
||||
|
||||
send = macro {
|
||||
offset += super._send(offset, bytes);
|
||||
};
|
||||
}
|
||||
|
||||
var index: Int = 0;
|
||||
for (field in fields) {
|
||||
var replicated = false;
|
||||
for (meta in field.meta) {
|
||||
if (meta.name == "replicate" || meta.name == "replicated") {
|
||||
replicated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!replicated)
|
||||
continue;
|
||||
|
||||
switch (field.kind) {
|
||||
case FVar(t, e):
|
||||
var fieldname = field.name;
|
||||
switch (t) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "Int":
|
||||
send = macro {
|
||||
$send;
|
||||
bytes.setInt32(offset + $v{index}, this.$fieldname);
|
||||
};
|
||||
receive = macro {
|
||||
$receive;
|
||||
this.$fieldname = bytes.getInt32(offset + $v{index});
|
||||
};
|
||||
index += 4;
|
||||
case "Float":
|
||||
send = macro {
|
||||
$send;
|
||||
bytes.setDouble(offset + $v{index}, this.$fieldname);
|
||||
};
|
||||
receive = macro {
|
||||
$receive;
|
||||
this.$fieldname = bytes.getDouble(offset + $v{index});
|
||||
};
|
||||
index += 8;
|
||||
case "Bool":
|
||||
send = macro {
|
||||
$send;
|
||||
bytes.set(offset + $v{index}, this.$fieldname ? 1 : 0);
|
||||
};
|
||||
receive = macro {
|
||||
$receive;
|
||||
this.$fieldname = bytes.get(offset + $v{index}) == 1 ? true : false;
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
default:
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
send = macro {
|
||||
$send;
|
||||
return $v{index};
|
||||
};
|
||||
|
||||
receive = macro {
|
||||
$receive;
|
||||
return $v{index};
|
||||
};
|
||||
|
||||
fields.push({
|
||||
name: "_send",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: isBaseEntity ? [APublic] : [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: Context.toComplexType(Context.getType("Int")),
|
||||
params: null,
|
||||
expr: send,
|
||||
args: [
|
||||
{
|
||||
value: null,
|
||||
type: Context.toComplexType(Context.getType("Int")),
|
||||
opt: null,
|
||||
name: "offset"
|
||||
},
|
||||
{
|
||||
value: null,
|
||||
type: Context.toComplexType(Context.getType("haxe.io.Bytes")),
|
||||
opt: null,
|
||||
name: "bytes"
|
||||
}
|
||||
]
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
fields.push({
|
||||
name: "_receive",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: isBaseEntity ? [APublic] : [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: Context.toComplexType(Context.getType("Int")),
|
||||
params: null,
|
||||
expr: receive,
|
||||
args: [
|
||||
{
|
||||
value: null,
|
||||
type: Context.toComplexType(Context.getType("Int")),
|
||||
opt: null,
|
||||
name: "offset"
|
||||
},
|
||||
{
|
||||
value: null,
|
||||
type: Context.toComplexType(Context.getType("haxe.io.Bytes")),
|
||||
opt: null,
|
||||
name: "bytes"
|
||||
}
|
||||
]
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
fields.push({
|
||||
name: "_id",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: isBaseEntity ? [APublic] : [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: Context.toComplexType(Context.getType("Int")),
|
||||
params: null,
|
||||
expr: macro {return __id;},
|
||||
args: []
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
var size = macro {
|
||||
return $v{index};
|
||||
};
|
||||
|
||||
if (!isBaseEntity) {
|
||||
size = macro {
|
||||
return super._size() + $v{index};
|
||||
};
|
||||
}
|
||||
|
||||
fields.push({
|
||||
name: "_size",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: isBaseEntity ? [APublic] : [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: Context.toComplexType(Context.getType("Int")),
|
||||
params: null,
|
||||
expr: size,
|
||||
args: []
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
if (isBaseEntity) {
|
||||
fields.push({
|
||||
name: "__id",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: [APublic],
|
||||
kind: FVar(macro : Int, macro kha.netsync.EntityBuilder.nextId++),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
15
Kha/Sources/kha/netsync/Example.hx
Normal file
15
Kha/Sources/kha/netsync/Example.hx
Normal file
@ -0,0 +1,15 @@
|
||||
package kha.netsync;
|
||||
|
||||
class Example implements Entity {
|
||||
@replicated
|
||||
var test: Float;
|
||||
@replicated
|
||||
var bla: Int;
|
||||
|
||||
public function new() {
|
||||
// super();
|
||||
test = 3;
|
||||
}
|
||||
|
||||
public function simulate(tdif: Float): Void {}
|
||||
}
|
29
Kha/Sources/kha/netsync/LocalClient.hx
Normal file
29
Kha/Sources/kha/netsync/LocalClient.hx
Normal file
@ -0,0 +1,29 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
class LocalClient implements Client {
|
||||
var myId: Int;
|
||||
|
||||
public function new(id: Int) {
|
||||
myId = id;
|
||||
}
|
||||
|
||||
public function send(bytes: Bytes, mandatory: Bool): Void {}
|
||||
|
||||
public function receive(receiver: Bytes->Void): Void {}
|
||||
|
||||
public function onClose(close: Void->Void): Void {}
|
||||
|
||||
public var controllers(get, null): Array<Controller>;
|
||||
|
||||
function get_controllers(): Array<Controller> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public var id(get, null): Int;
|
||||
|
||||
function get_id(): Int {
|
||||
return myId;
|
||||
}
|
||||
}
|
9
Kha/Sources/kha/netsync/Network.hx
Normal file
9
Kha/Sources/kha/netsync/Network.hx
Normal file
@ -0,0 +1,9 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
extern class Network {
|
||||
public function new(url: String, port: Int, errorCallback: Void->Void, closeCallback: Void->Void);
|
||||
public function send(bytes: Bytes, mandatory: Bool): Void;
|
||||
public function listen(listener: Bytes->Void): Void;
|
||||
}
|
57
Kha/Sources/kha/netsync/NodeProcessClient.hx
Normal file
57
Kha/Sources/kha/netsync/NodeProcessClient.hx
Normal file
@ -0,0 +1,57 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
#if js
|
||||
import js.Node;
|
||||
import js.node.Buffer;
|
||||
#end
|
||||
|
||||
class NodeProcessClient implements Client {
|
||||
var myId: Int = -1;
|
||||
var receiveCallback: Bytes->Void = null;
|
||||
var closeCallback: Void->Void = null;
|
||||
|
||||
public function new(id: Int) {
|
||||
myId = id;
|
||||
}
|
||||
|
||||
public function send(bytes: Bytes, mandatory: Bool): Void {
|
||||
#if js
|
||||
var data = bytes.getData();
|
||||
var buffer = js.Syntax.code("new Buffer(data)");
|
||||
Node.process.send({data: buffer.toString(), id: myId});
|
||||
#end
|
||||
}
|
||||
|
||||
public function receive(receiver: Bytes->Void): Void {
|
||||
receiveCallback = receiver;
|
||||
}
|
||||
|
||||
public function _message(data): Void {
|
||||
if (receiveCallback != null) {
|
||||
receiveCallback(Bytes.ofString(data));
|
||||
}
|
||||
}
|
||||
|
||||
public function onClose(close: Void->Void): Void {
|
||||
closeCallback = close;
|
||||
}
|
||||
|
||||
public function _close(): Void {
|
||||
if (closeCallback != null) {
|
||||
closeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public var controllers(get, null): Array<Controller>;
|
||||
|
||||
function get_controllers(): Array<Controller> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public var id(get, null): Int;
|
||||
|
||||
function get_id(): Int {
|
||||
return myId;
|
||||
}
|
||||
}
|
129
Kha/Sources/kha/netsync/Server.hx
Normal file
129
Kha/Sources/kha/netsync/Server.hx
Normal file
@ -0,0 +1,129 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
#if sys_server
|
||||
import js.Node;
|
||||
import js.node.Buffer;
|
||||
import js.node.Dgram;
|
||||
#end
|
||||
|
||||
class Server {
|
||||
#if sys_server
|
||||
private var app: Dynamic;
|
||||
private var udpSocket: Dynamic;
|
||||
private var udpClients: Map<String, UdpClient> = new Map();
|
||||
private var lastId: Int = -1;
|
||||
|
||||
#if !direct_connection
|
||||
private var clients: Map<Int, NodeProcessClient> = new Map();
|
||||
private var connectionCallback: Client->Void;
|
||||
#end
|
||||
#end
|
||||
public function new(port: Int) {
|
||||
#if sys_server
|
||||
#if direct_connection
|
||||
var express = Node.require("express");
|
||||
app = express();
|
||||
Node.require("express-ws")(app);
|
||||
|
||||
app.use('/', untyped __js__("express.static('../html5')"));
|
||||
|
||||
app.use(function(err, req, res, next) {
|
||||
Node.console.error(err.stack);
|
||||
});
|
||||
|
||||
app.listen(port);
|
||||
|
||||
udpSocket = Dgram.createSocket("udp4");
|
||||
udpSocket.bind(port +
|
||||
1); // TODO: This is somewhat ugly, but necessary to maintain both websocket and UPD connections at the same time (see also kore/network/Network.hx)
|
||||
#else
|
||||
Node.process.on("message", function(message) {
|
||||
var msg: String = message.message;
|
||||
switch (msg) {
|
||||
case "connect":
|
||||
{
|
||||
var id: Int = message.id;
|
||||
var client = new NodeProcessClient(id);
|
||||
clients[id] = client;
|
||||
connectionCallback(client);
|
||||
}
|
||||
case "disconnect":
|
||||
{
|
||||
var id: Int = message.id;
|
||||
var client = clients[id];
|
||||
client._close();
|
||||
clients.remove(id);
|
||||
}
|
||||
case "message":
|
||||
{
|
||||
var id: Int = message.id;
|
||||
var client = clients[id];
|
||||
client._message(message.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
public function onConnection(connection: Client->Void): Void {
|
||||
#if sys_server
|
||||
#if direct_connection
|
||||
app.ws('/', function(socket, req) {
|
||||
++lastId;
|
||||
connection(new WebSocketClient(lastId, socket));
|
||||
});
|
||||
|
||||
udpSocket.on('message', function(message: Buffer, info) {
|
||||
if (compare(message, "JOIN")) {
|
||||
++lastId;
|
||||
var client = new UdpClient(lastId, udpSocket, info.address, info.port);
|
||||
udpClients.set(info.address + info.port, client);
|
||||
connection(client);
|
||||
// var content = Bytes.alloc(4);
|
||||
// content.setInt32(0, lastId);
|
||||
// client.send(content, true);
|
||||
}
|
||||
else {
|
||||
var client = udpClients.get(info.address + info.port);
|
||||
if (client != null) {
|
||||
if (client.onReceive != null) {
|
||||
var data: haxe.io.BytesData = cast message;
|
||||
client.onReceive(Bytes.ofData(data));
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
/*var id: Int = message[0];
|
||||
var client = clients[id];
|
||||
var content = Bytes.alloc(message.length - 1);
|
||||
content.blit(0, message, 1, message.length);
|
||||
client._message(message.length); */
|
||||
}
|
||||
// TODO: Timeout for disconnect
|
||||
// console.log('Received %d bytes from %s:%d\n', message.length, info.address, info.port);
|
||||
});
|
||||
#else
|
||||
connectionCallback = connection;
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
public function reset(): Void {
|
||||
#if sys_server
|
||||
lastId = -1;
|
||||
#end
|
||||
}
|
||||
|
||||
#if sys_server
|
||||
private static function compare(buffer: Buffer, message: String): Bool {
|
||||
if (buffer.length != message.length)
|
||||
return false;
|
||||
for (i in 0...buffer.length) {
|
||||
if (buffer.readUInt8(i) != message.charCodeAt(i))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#end
|
||||
}
|
454
Kha/Sources/kha/netsync/Session.hx
Normal file
454
Kha/Sources/kha/netsync/Session.hx
Normal file
@ -0,0 +1,454 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
#if sys_server
|
||||
import js.node.Http;
|
||||
import js.Node;
|
||||
#end
|
||||
#if js
|
||||
import js.Browser;
|
||||
import js.html.BinaryType;
|
||||
import js.html.WebSocket;
|
||||
#end
|
||||
import kha.System;
|
||||
|
||||
class State {
|
||||
public var time: Float;
|
||||
public var data: Bytes;
|
||||
|
||||
public function new(time: Float, data: Bytes) {
|
||||
this.time = time;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
class Session {
|
||||
public static inline var START = 0;
|
||||
public static inline var ENTITY_UPDATES = 1;
|
||||
public static inline var CONTROLLER_UPDATES = 2;
|
||||
public static inline var REMOTE_CALL = 3;
|
||||
public static inline var PING = 4;
|
||||
public static inline var SESSION_ERROR = 5;
|
||||
public static inline var PLAYER_UPDATES = 6;
|
||||
|
||||
public static inline var RPC_SERVER = 0;
|
||||
public static inline var RPC_ALL = 1;
|
||||
|
||||
static var instance: Session = null;
|
||||
|
||||
var entities: Map<Int, Entity> = new Map();
|
||||
var controllers: Map<Int, Controller> = new Map();
|
||||
|
||||
public var maxPlayers: Int;
|
||||
public var currentPlayers: Int = 0;
|
||||
public var ping: Float = 1;
|
||||
|
||||
var address: String;
|
||||
var port: Int;
|
||||
var startCallback: Void->Void;
|
||||
var refusedCallback: Void->Void;
|
||||
var resetCallback: Void->Void;
|
||||
#if sys_server
|
||||
var server: Server;
|
||||
var clients: Array<Client> = new Array();
|
||||
var current: Client;
|
||||
var isJoinable: Bool = false;
|
||||
var lastStates: Array<State> = new Array();
|
||||
|
||||
static inline var stateCount = 60 * 10; // 10 seconds with 60 fps
|
||||
|
||||
#else
|
||||
var localClient: Client;
|
||||
|
||||
public var network: Network;
|
||||
|
||||
var updateTaskId: Int;
|
||||
var pingTaskId: Int;
|
||||
#end
|
||||
|
||||
public var me(get, null): Client;
|
||||
|
||||
function get_me(): Client {
|
||||
#if sys_server
|
||||
return current;
|
||||
#else
|
||||
return localClient;
|
||||
#end
|
||||
}
|
||||
|
||||
public function new(maxPlayers: Int, address: String, port: Int) {
|
||||
instance = this;
|
||||
this.maxPlayers = maxPlayers;
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public static function the(): Session {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public function addEntity(entity: Entity): Void {
|
||||
entities.set(entity._id(), entity);
|
||||
}
|
||||
|
||||
public function addController(controller: Controller): Void {
|
||||
trace("Adding controller id " + controller._id());
|
||||
controller._inputBufferIndex = 0;
|
||||
controllers.set(controller._id(), controller);
|
||||
}
|
||||
|
||||
#if sys_server
|
||||
private function send(): Bytes {
|
||||
var size = 0;
|
||||
for (entity in entities) {
|
||||
size += entity._size();
|
||||
}
|
||||
var offset = 0;
|
||||
var bytes = Bytes.alloc(size + 9);
|
||||
bytes.set(offset, ENTITY_UPDATES);
|
||||
offset += 1;
|
||||
bytes.setDouble(offset, Scheduler.time());
|
||||
offset += 8;
|
||||
for (entity in entities) {
|
||||
entity._send(offset, bytes);
|
||||
offset += entity._size();
|
||||
}
|
||||
|
||||
lastStates.push(new State(Scheduler.time(), bytes));
|
||||
if (lastStates.length > stateCount) {
|
||||
lastStates.splice(0, 1);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
#end
|
||||
|
||||
public function sendControllerUpdate(id: Int, bytes: haxe.io.Bytes) {
|
||||
#if !sys_server
|
||||
if (controllers.exists(id)) {
|
||||
if (controllers[id]._inputBuffer.length < controllers[id]._inputBufferIndex + 4 + bytes.length) {
|
||||
var newBuffer = Bytes.alloc(controllers[id]._inputBufferIndex + 4 + bytes.length);
|
||||
newBuffer.blit(0, controllers[id]._inputBuffer, 0, controllers[id]._inputBufferIndex);
|
||||
controllers[id]._inputBuffer = newBuffer;
|
||||
}
|
||||
|
||||
controllers[id]._inputBuffer.setInt32(controllers[id]._inputBufferIndex, bytes.length);
|
||||
controllers[id]._inputBuffer.blit(controllers[id]._inputBufferIndex + 4, bytes, 0, bytes.length);
|
||||
controllers[id]._inputBufferIndex += (4 + bytes.length);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
function sendPing() {
|
||||
#if !sys_server
|
||||
var bytes = haxe.io.Bytes.alloc(5);
|
||||
bytes.set(0, kha.netsync.Session.PING);
|
||||
bytes.setFloat(1, Scheduler.realTime());
|
||||
|
||||
sendToServer(bytes);
|
||||
#end
|
||||
}
|
||||
|
||||
function sendPlayerUpdate() {
|
||||
#if sys_server
|
||||
currentPlayers = clients.length;
|
||||
var bytes = haxe.io.Bytes.alloc(5);
|
||||
bytes.set(0, PLAYER_UPDATES);
|
||||
bytes.setInt32(1, currentPlayers);
|
||||
|
||||
sendToEverybody(bytes);
|
||||
#end
|
||||
}
|
||||
|
||||
public function receive(bytes: Bytes, client: Client = null): Void {
|
||||
#if sys_server
|
||||
switch (bytes.get(0)) {
|
||||
case CONTROLLER_UPDATES:
|
||||
var id = bytes.getInt32(1);
|
||||
var time = bytes.getDouble(5);
|
||||
|
||||
var width = bytes.getInt32(13);
|
||||
var height = bytes.getInt32(17);
|
||||
var rotation = bytes.get(21);
|
||||
SystemImpl._updateSize(width, height);
|
||||
SystemImpl._updateScreenRotation(rotation);
|
||||
|
||||
if (controllers.exists(id)) {
|
||||
processEventRetroactively(function() {
|
||||
current = client;
|
||||
var offset = 22;
|
||||
while (offset < bytes.length) {
|
||||
var length = bytes.getInt32(offset);
|
||||
controllers[id]._receive(bytes.sub(offset + 4, length));
|
||||
offset += (4 + length);
|
||||
}
|
||||
current = null;
|
||||
}, time);
|
||||
}
|
||||
case REMOTE_CALL:
|
||||
processRPC(bytes);
|
||||
case PING:
|
||||
// PONG, i.e. just return the packet to the client
|
||||
if (client != null)
|
||||
client.send(bytes, false);
|
||||
}
|
||||
#else
|
||||
switch (bytes.get(0)) {
|
||||
case START:
|
||||
var index = bytes.get(1);
|
||||
localClient = new LocalClient(index);
|
||||
Scheduler.resetTime();
|
||||
startCallback();
|
||||
case ENTITY_UPDATES:
|
||||
var time = bytes.getDouble(1);
|
||||
var offset = 9;
|
||||
for (entity in entities) {
|
||||
entity._receive(offset, bytes);
|
||||
offset += entity._size();
|
||||
}
|
||||
Scheduler.warp(time);
|
||||
case REMOTE_CALL:
|
||||
switch (bytes.get(1)) {
|
||||
case RPC_SERVER:
|
||||
// Mainly a safeguard, packets with RPC_SERVER should not be received here
|
||||
case RPC_ALL:
|
||||
executeRPC(bytes);
|
||||
}
|
||||
case PING:
|
||||
var sendTime = bytes.getFloat(1);
|
||||
ping = Scheduler.realTime() - sendTime;
|
||||
case SESSION_ERROR:
|
||||
refusedCallback();
|
||||
case PLAYER_UPDATES:
|
||||
currentPlayers = bytes.getInt32(1);
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
#if sys_server
|
||||
private function processEventRetroactively(event: Void->Void, time: Float) {
|
||||
if (time <= Scheduler.time()) {
|
||||
// var temp = time;
|
||||
// Process after earliest saved state if it is too far back
|
||||
if (time <= lastStates[0].time) {
|
||||
time = lastStates[0].time + 0.00001;
|
||||
}
|
||||
|
||||
var i = lastStates.length - 1;
|
||||
while (i >= 0) {
|
||||
if (lastStates[i].time < time) {
|
||||
var offset = 9;
|
||||
for (entity in entities) {
|
||||
entity._receive(offset, lastStates[i].data);
|
||||
offset += entity._size();
|
||||
}
|
||||
// Invalidate states in which the new event is missing
|
||||
if (i < lastStates.length - 1) {
|
||||
lastStates.splice(i + 1, lastStates.length - i - 1);
|
||||
}
|
||||
Scheduler.warp(lastStates[i].time);
|
||||
break;
|
||||
}
|
||||
--i;
|
||||
}
|
||||
}
|
||||
Scheduler.addTimeTask(event, time - Scheduler.time());
|
||||
}
|
||||
#end
|
||||
|
||||
#if sys_server
|
||||
public function processRPC(bytes: Bytes) {
|
||||
switch (bytes.get(1)) {
|
||||
case RPC_SERVER:
|
||||
executeRPC(bytes);
|
||||
case RPC_ALL:
|
||||
sendToEverybody(bytes);
|
||||
executeRPC(bytes);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
function executeRPC(bytes: Bytes) {
|
||||
var args = new Array<Dynamic>();
|
||||
var syncId = bytes.getInt32(2);
|
||||
var index: Int = 6;
|
||||
|
||||
var classnamelength = bytes.getUInt16(index);
|
||||
index += 2;
|
||||
var classname = "";
|
||||
for (i in 0...classnamelength) {
|
||||
classname += String.fromCharCode(bytes.get(index));
|
||||
++index;
|
||||
}
|
||||
|
||||
var methodnamelength = bytes.getUInt16(index);
|
||||
index += 2;
|
||||
var methodname = "";
|
||||
for (i in 0...methodnamelength) {
|
||||
methodname += String.fromCharCode(bytes.get(index));
|
||||
++index;
|
||||
}
|
||||
|
||||
while (index < bytes.length) {
|
||||
var type = bytes.get(index);
|
||||
++index;
|
||||
switch (type) {
|
||||
case 0x42: // B
|
||||
var value: Bool = bytes.get(index) == 1;
|
||||
++index;
|
||||
trace("Bool: " + value);
|
||||
args.push(value);
|
||||
case 0x46: // F
|
||||
var value: Float = bytes.getDouble(index);
|
||||
index += 8;
|
||||
trace("Float: " + value);
|
||||
args.push(value);
|
||||
case 0x49: // I
|
||||
var value: Int = bytes.getInt32(index);
|
||||
index += 4;
|
||||
trace("Int: " + value);
|
||||
args.push(value);
|
||||
case 0x53: // S
|
||||
var length = bytes.getUInt16(index);
|
||||
index += 2;
|
||||
var str = "";
|
||||
for (i in 0...length) {
|
||||
str += String.fromCharCode(bytes.get(index));
|
||||
++index;
|
||||
}
|
||||
trace("String: " + str);
|
||||
args.push(str);
|
||||
default:
|
||||
trace("Unknown argument type.");
|
||||
}
|
||||
}
|
||||
if (syncId == -1) {
|
||||
Reflect.callMethod(null, Reflect.field(Type.resolveClass(classname), methodname + "_remotely"), args);
|
||||
}
|
||||
else {
|
||||
Reflect.callMethod(SyncBuilder.objects[syncId], Reflect.field(SyncBuilder.objects[syncId], methodname + "_remotely"), args);
|
||||
}
|
||||
}
|
||||
|
||||
public function waitForStart(callback: Void->Void, refuseCallback: Void->Void, errorCallback: Void->Void, closeCallback: Void->Void,
|
||||
resCallback: Void->Void): Void {
|
||||
startCallback = callback;
|
||||
refusedCallback = refuseCallback;
|
||||
resetCallback = resCallback;
|
||||
#if sys_server
|
||||
isJoinable = true;
|
||||
#if direct_connection
|
||||
trace("Starting server at " + port + ".");
|
||||
#end
|
||||
server = new Server(port);
|
||||
server.onConnection(function(client: Client) {
|
||||
if (!isJoinable) {
|
||||
var bytes = Bytes.alloc(1);
|
||||
bytes.set(0, SESSION_ERROR);
|
||||
client.send(bytes, true);
|
||||
return;
|
||||
}
|
||||
|
||||
clients.push(client);
|
||||
current = client;
|
||||
|
||||
Node.console.log(clients.length + " client" + (clients.length > 1 ? "s " : " ") + "connected.");
|
||||
sendPlayerUpdate();
|
||||
|
||||
client.receive(function(bytes: Bytes) {
|
||||
receive(bytes, client);
|
||||
});
|
||||
|
||||
client.onClose(function() {
|
||||
Node.console.log("Removing client " + client.id + ".");
|
||||
clients.remove(client);
|
||||
sendPlayerUpdate();
|
||||
// isJoinable is intentionally not reset here immediately, as late joining is currently unsupported
|
||||
if (clients.length == 0) {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
if (clients.length >= maxPlayers) {
|
||||
isJoinable = false;
|
||||
Node.console.log("Starting game.");
|
||||
var index = 0;
|
||||
for (c in clients) {
|
||||
trace("Starting client " + c.id);
|
||||
var bytes = Bytes.alloc(2);
|
||||
bytes.set(0, START);
|
||||
bytes.set(1, index);
|
||||
c.send(bytes, true);
|
||||
++index;
|
||||
}
|
||||
Scheduler.resetTime();
|
||||
startCallback();
|
||||
}
|
||||
});
|
||||
#else
|
||||
network = new Network(address, port, errorCallback, function() {
|
||||
closeCallback();
|
||||
reset();
|
||||
});
|
||||
network.listen(function(bytes: Bytes) {
|
||||
receive(bytes);
|
||||
});
|
||||
updateTaskId = Scheduler.addFrameTask(update, 0);
|
||||
ping = 1;
|
||||
pingTaskId = Scheduler.addTimeTask(sendPing, 0, 1);
|
||||
#end
|
||||
}
|
||||
|
||||
function reset() {
|
||||
#if sys_server
|
||||
isJoinable = true;
|
||||
server.reset();
|
||||
#else
|
||||
Scheduler.removeFrameTask(updateTaskId);
|
||||
Scheduler.removeTimeTask(pingTaskId);
|
||||
#end
|
||||
currentPlayers = 0;
|
||||
ping = 1;
|
||||
controllers = new Map();
|
||||
entities = new Map();
|
||||
resetCallback();
|
||||
}
|
||||
|
||||
public function update(): Void {
|
||||
#if sys_server
|
||||
var bytes = send();
|
||||
sendToEverybody(bytes);
|
||||
#else
|
||||
for (controller in controllers) {
|
||||
if (controller._inputBufferIndex > 0) {
|
||||
var bytes = haxe.io.Bytes.alloc(22 + controller._inputBufferIndex);
|
||||
bytes.set(0, kha.netsync.Session.CONTROLLER_UPDATES);
|
||||
bytes.setInt32(1, controller._id());
|
||||
bytes.setDouble(5, Scheduler.time());
|
||||
bytes.setInt32(13, System.windowWidth(0));
|
||||
bytes.setInt32(17, System.windowHeight(0));
|
||||
bytes.set(21, 0); // System.screenRotation.getIndex());
|
||||
|
||||
bytes.blit(22, controller._inputBuffer, 0, controller._inputBufferIndex);
|
||||
|
||||
sendToServer(bytes);
|
||||
controller._inputBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
#if sys_server
|
||||
public function sendToEverybody(bytes: Bytes): Void {
|
||||
for (client in clients) {
|
||||
client.send(bytes, false);
|
||||
}
|
||||
}
|
||||
#end
|
||||
|
||||
#if !sys_server
|
||||
public function sendToServer(bytes: Bytes): Void {
|
||||
network.send(bytes, false);
|
||||
}
|
||||
#end
|
||||
}
|
6
Kha/Sources/kha/netsync/Sync.hx
Normal file
6
Kha/Sources/kha/netsync/Sync.hx
Normal file
@ -0,0 +1,6 @@
|
||||
package kha.netsync;
|
||||
|
||||
@:autoBuild(kha.netsync.SyncBuilder.build())
|
||||
interface Sync {
|
||||
function _syncId(): Int;
|
||||
}
|
263
Kha/Sources/kha/netsync/SyncBuilder.hx
Normal file
263
Kha/Sources/kha/netsync/SyncBuilder.hx
Normal file
@ -0,0 +1,263 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Expr.Field;
|
||||
|
||||
class SyncBuilder {
|
||||
public static var nextId: Int = 0;
|
||||
public static var objects: Array<Dynamic> = new Array<Dynamic>();
|
||||
|
||||
macro static public function build(): Array<Field> {
|
||||
var fields = Context.getBuildFields();
|
||||
|
||||
var isBaseEntity = false;
|
||||
for (i in Context.getLocalClass().get().interfaces) {
|
||||
var intf = i.t.get();
|
||||
if (intf.module == "kha.netsync.Sync") {
|
||||
isBaseEntity = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (field in fields) {
|
||||
if (field.name == "new") {
|
||||
switch (field.kind) {
|
||||
case FFun(f):
|
||||
var cexpr = f.expr;
|
||||
cexpr = macro @:mergeBlock {
|
||||
$cexpr;
|
||||
kha.netsync.SyncBuilder.objects[_syncId()] = this;
|
||||
}
|
||||
f.expr = cexpr;
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
var synced = false;
|
||||
// TODO: Avoid hardcoding the target ids
|
||||
var target = 0; // kha.netsync.Session.RPC_SERVER;
|
||||
var isStatic = field.access.lastIndexOf(AStatic) >= 0;
|
||||
for (meta in field.meta) {
|
||||
if (meta.name == "sync" || meta.name == "synced") {
|
||||
// TODO: Figure out if there is a "nicer" way to do this
|
||||
for (param in meta.params) {
|
||||
if (param.expr.equals(EConst(CString("server")))) {
|
||||
target = 0; // kha.netsync.Session.RPC_SERVER;
|
||||
break;
|
||||
}
|
||||
else if (param.expr.equals(EConst(CString("all")))) {
|
||||
target = 1; // kha.netsync.Session.RPC_ALL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
synced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!synced)
|
||||
continue;
|
||||
|
||||
switch (field.kind) {
|
||||
case FFun(f):
|
||||
var original = f.expr;
|
||||
|
||||
var size = 6;
|
||||
for (arg in f.args) {
|
||||
switch (arg.type) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "Int":
|
||||
size += 5;
|
||||
case "Float":
|
||||
size += 9;
|
||||
case "Bool":
|
||||
size += 2;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
var expr = macro @:mergeBlock {
|
||||
var size: Int = $v{size};
|
||||
};
|
||||
|
||||
for (arg in f.args) {
|
||||
switch (arg.type) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "String":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
size += $i{argname}.length + 3;
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
var classname = Context.getLocalClass().toString();
|
||||
var methodname = field.name;
|
||||
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
size += $v{classname}.length + 2;
|
||||
size += $v{methodname}.length + 2;
|
||||
var bytes = haxe.io.Bytes.alloc(size);
|
||||
bytes.set(0, kha.netsync.Session.REMOTE_CALL);
|
||||
bytes.set(1, $v{target});
|
||||
}
|
||||
|
||||
if (isStatic) {
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.setInt32(2, -1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.setInt32(2, _syncId());
|
||||
}
|
||||
}
|
||||
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
var index = 6;
|
||||
|
||||
bytes.setUInt16(index, $v{classname}.length);
|
||||
index += 2;
|
||||
for (i in 0...$v{classname}.length) {
|
||||
bytes.set(index, $v{classname}.charCodeAt(i));
|
||||
++index;
|
||||
}
|
||||
|
||||
bytes.setUInt16(index, $v{methodname}.length);
|
||||
index += 2;
|
||||
for (i in 0...$v{methodname}.length) {
|
||||
bytes.set(index, $v{methodname}.charCodeAt(i));
|
||||
++index;
|
||||
}
|
||||
};
|
||||
for (arg in f.args) {
|
||||
switch (arg.type) {
|
||||
case TPath(p):
|
||||
switch (p.name) {
|
||||
case "Int":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set(index, 'I'.charCodeAt(0));
|
||||
++index;
|
||||
bytes.setInt32(index, $i{argname});
|
||||
index += 4;
|
||||
};
|
||||
case "String":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set(index, 'S'.charCodeAt(0));
|
||||
++index;
|
||||
bytes.setUInt16(index, $i{argname}.length);
|
||||
index += 2;
|
||||
for (i in 0...$i{argname}.length) {
|
||||
bytes.set(index, $i{argname}.charCodeAt(i));
|
||||
++index;
|
||||
}
|
||||
};
|
||||
case "Float":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set(index, 'F'.charCodeAt(0));
|
||||
++index;
|
||||
bytes.setDouble(index, $i{argname});
|
||||
index += 8;
|
||||
};
|
||||
case "Bool":
|
||||
var argname = arg.name;
|
||||
expr = macro @:mergeBlock {
|
||||
$expr;
|
||||
bytes.set(index, 'B'.charCodeAt(0));
|
||||
++index;
|
||||
bytes.set(index, $i{argname} ?1:0);
|
||||
++index;
|
||||
};
|
||||
default:
|
||||
trace("Warning: type '" + p.name + "' of property '" + arg.name + "' cannot be synced");
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
#if sys_server
|
||||
expr = macro {
|
||||
if (kha.netsync.Session.the() != null) {
|
||||
$expr;
|
||||
kha.netsync.Session.the().processRPC(bytes);
|
||||
}
|
||||
};
|
||||
#else
|
||||
expr = macro {
|
||||
if (kha.netsync.Session.the() != null) {
|
||||
$expr;
|
||||
kha.netsync.Session.the().sendToServer(bytes);
|
||||
}
|
||||
else {
|
||||
$original;
|
||||
}
|
||||
};
|
||||
#end
|
||||
|
||||
fields.push({
|
||||
name: field.name + "_remotely",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: (isStatic ? [APublic, AStatic] : [APublic]),
|
||||
kind: FFun({
|
||||
ret: f.ret,
|
||||
params: f.params,
|
||||
expr: original,
|
||||
args: f.args
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
f.expr = expr;
|
||||
default:
|
||||
trace("Warning: Synced property " + field.name + " is not a function.");
|
||||
}
|
||||
}
|
||||
|
||||
fields.push({
|
||||
name: "_syncId",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: isBaseEntity ? [APublic] : [APublic, AOverride],
|
||||
kind: FFun({
|
||||
ret: Context.toComplexType(Context.getType("Int")),
|
||||
params: null,
|
||||
expr: macro {
|
||||
return __syncId;
|
||||
},
|
||||
args: []
|
||||
}),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
|
||||
if (isBaseEntity) {
|
||||
fields.push({
|
||||
name: "__syncId",
|
||||
doc: null,
|
||||
meta: [],
|
||||
access: [APublic],
|
||||
kind: FVar(macro : Int, macro kha.netsync.SyncBuilder.nextId++),
|
||||
pos: Context.currentPos()
|
||||
});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
56
Kha/Sources/kha/netsync/UdpClient.hx
Normal file
56
Kha/Sources/kha/netsync/UdpClient.hx
Normal file
@ -0,0 +1,56 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
#if sys_server
|
||||
import js.node.Buffer;
|
||||
import js.node.Dgram;
|
||||
#end
|
||||
|
||||
class UdpClient implements Client {
|
||||
var myId: Int;
|
||||
|
||||
public var onReceive: Bytes->Void = null;
|
||||
|
||||
#if sys_server
|
||||
var socket: Dynamic;
|
||||
#end
|
||||
var address: String;
|
||||
var port: Int;
|
||||
|
||||
#if sys_server
|
||||
public function new(id: Int, socket: Dynamic, address: String, port: Int) {
|
||||
myId = id;
|
||||
this.socket = socket;
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
#end
|
||||
|
||||
public function send(bytes: Bytes, mandatory: Bool): Void {
|
||||
#if sys_server
|
||||
var buffer = new Buffer(bytes.length);
|
||||
for (i in 0...bytes.length) {
|
||||
buffer[i] = bytes.get(i);
|
||||
}
|
||||
socket.send(buffer, 0, bytes.length, port, address);
|
||||
#end
|
||||
}
|
||||
|
||||
public function receive(receiver: Bytes->Void): Void {
|
||||
onReceive = receiver;
|
||||
}
|
||||
|
||||
public function onClose(close: Void->Void): Void {}
|
||||
|
||||
public var controllers(get, null): Array<Controller>;
|
||||
|
||||
function get_controllers(): Array<Controller> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public var id(get, null): Int;
|
||||
|
||||
function get_id(): Int {
|
||||
return myId;
|
||||
}
|
||||
}
|
43
Kha/Sources/kha/netsync/WebSocketClient.hx
Normal file
43
Kha/Sources/kha/netsync/WebSocketClient.hx
Normal file
@ -0,0 +1,43 @@
|
||||
package kha.netsync;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
|
||||
class WebSocketClient implements Client {
|
||||
var myId: Int;
|
||||
var socket: Dynamic;
|
||||
|
||||
public function new(id: Int, socket: Dynamic) {
|
||||
myId = id;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public function send(bytes: Bytes, mandatory: Bool): Void {
|
||||
socket.send(bytes.getData());
|
||||
// socket.send(bytes.getData(), {binary: true});
|
||||
}
|
||||
|
||||
public function receive(receiver: Bytes->Void): Void {
|
||||
socket.on('message', function(message) {
|
||||
// js.Node.console.log(message);
|
||||
receiver(Bytes.ofData(message));
|
||||
});
|
||||
}
|
||||
|
||||
public function onClose(close: Void->Void): Void {
|
||||
socket.onclose = function() {
|
||||
close();
|
||||
};
|
||||
}
|
||||
|
||||
public var controllers(get, null): Array<Controller>;
|
||||
|
||||
function get_controllers(): Array<Controller> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public var id(get, null): Int;
|
||||
|
||||
function get_id(): Int {
|
||||
return myId;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user