Update Files
This commit is contained in:
131
leenkx/Sources/leenkx/system/Assert.hx
Normal file
131
leenkx/Sources/leenkx/system/Assert.hx
Normal file
@ -0,0 +1,131 @@
|
||||
package leenkx.system;
|
||||
|
||||
import haxe.Exception;
|
||||
import haxe.PosInfos;
|
||||
import haxe.exceptions.PosException;
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
using haxe.macro.ExprTools;
|
||||
|
||||
class Assert {
|
||||
|
||||
/**
|
||||
Checks whether the given expression evaluates to true. If this is not
|
||||
the case, an `LnxAssertionException` is thrown or a warning is printed
|
||||
(depending on the assertion level).
|
||||
|
||||
The assert level describes the severity of the assertion. If the
|
||||
severity is lower than the level stored in the `lnx_assert_level` flag,
|
||||
the assertion is omitted from the code so that it doesn't decrease the
|
||||
runtime performance.
|
||||
|
||||
@param level The severity of this assertion.
|
||||
@param condition The conditional expression to test.
|
||||
@param message Optional message to display when the assertion fails.
|
||||
|
||||
@see `AssertLevel`
|
||||
**/
|
||||
macro public static function assert(level: ExprOf<AssertLevel>, condition: ExprOf<Bool>, ?message: ExprOf<String>): Expr {
|
||||
final levelVal: AssertLevel = AssertLevel.fromExpr(level);
|
||||
final assertThreshold = AssertLevel.fromString(Context.definedValue("lnx_assert_level"));
|
||||
|
||||
if (levelVal < assertThreshold) {
|
||||
return macro {};
|
||||
}
|
||||
|
||||
switch (levelVal) {
|
||||
case Warning:
|
||||
return macro {
|
||||
if (!$condition) {
|
||||
@:pos(condition.pos)
|
||||
trace(@:privateAccess leenkx.system.Assert.LnxAssertionException.formatMessage($v{condition.toString()}, ${message}));
|
||||
}
|
||||
}
|
||||
case Error:
|
||||
return macro {
|
||||
if (!$condition) {
|
||||
#if lnx_assert_quit kha.System.stop(); #end
|
||||
|
||||
@:pos(condition.pos)
|
||||
@:privateAccess leenkx.system.Assert.throwAssertionError($v{condition.toString()}, ${message});
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Exception('Unsupported assert level: $levelVal');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Helper function to prevent Haxe "bug" that actually throws an error
|
||||
even when using `macro throw` (inlining this method also does not work).
|
||||
**/
|
||||
static function throwAssertionError(exprString: String, message: String, ?pos: PosInfos) {
|
||||
throw new LnxAssertionException(exprString, message, pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Exception that is thrown when an assertion fails.
|
||||
|
||||
@see `Assert`
|
||||
**/
|
||||
class LnxAssertionException extends PosException {
|
||||
|
||||
/**
|
||||
@param exprString The string representation of the failed assert condition.
|
||||
@param message Custom error message, use `null` to omit this.
|
||||
**/
|
||||
public inline function new(exprString: String, message: Null<String>, ?previous: Exception, ?pos: Null<PosInfos>) {
|
||||
super('\n${formatMessage(exprString, message)}', previous, pos);
|
||||
}
|
||||
|
||||
static inline function formatMessage(exprString: String, message: Null<String>): String {
|
||||
final optMsg = message != null ? '\n\tMessage: $message' : "";
|
||||
|
||||
return 'Failed assertion:$optMsg\n\tExpression: ($exprString)';
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract AssertLevel(Int) from Int to Int {
|
||||
/**
|
||||
Assertions with this severity don't throw exceptions and only print to
|
||||
the console.
|
||||
**/
|
||||
var Warning: AssertLevel = 0;
|
||||
|
||||
/**
|
||||
Assertions with this severity throw an `LnxAssertionException` if they
|
||||
fail, and optionally quit the game if the `lnx_assert_quit` flag is set.
|
||||
**/
|
||||
var Error: AssertLevel = 1;
|
||||
|
||||
/**
|
||||
Completely disable assertions. Don't use this level in `assert()` calls!
|
||||
**/
|
||||
var NoAssertions: AssertLevel = 2;
|
||||
|
||||
public static function fromExpr(e: ExprOf<AssertLevel>): AssertLevel {
|
||||
switch (e.expr) {
|
||||
case EConst(CIdent(v)): return fromString(v);
|
||||
default: throw new Exception('Unsupported expression: $e');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Converts a string into an `AssertLevel`, the string must be spelled
|
||||
exactly as the assert level. `null` defaults to
|
||||
`AssertLevel.NoAssertions`.
|
||||
**/
|
||||
public static function fromString(s: Null<String>): AssertLevel {
|
||||
return switch (s) {
|
||||
case "Warning": Warning;
|
||||
case "Error": Error;
|
||||
case "NoAssertions" | null: NoAssertions;
|
||||
default: throw new Exception('Could not convert "$s" to AssertLevel');
|
||||
}
|
||||
}
|
||||
|
||||
@:op(A < B) static function lt(a: AssertLevel, b: AssertLevel): Bool;
|
||||
@:op(A > B) static function gt(a: AssertLevel, b: AssertLevel): Bool;
|
||||
}
|
||||
83
leenkx/Sources/leenkx/system/Event.hx
Normal file
83
leenkx/Sources/leenkx/system/Event.hx
Normal file
@ -0,0 +1,83 @@
|
||||
package leenkx.system;
|
||||
|
||||
/**
|
||||
Detailed documentation of the event system:
|
||||
[Leenkx Wiki: Events](https://github.com/leenkx3d/leenkx/wiki/events).
|
||||
**/
|
||||
class Event {
|
||||
|
||||
static var events = new Map<String, Array<TEvent>>();
|
||||
|
||||
/**
|
||||
Send an event with the given name to all corresponding listeners. This
|
||||
function directly executes the `onEvent` callbacks of those listeners.
|
||||
|
||||
For an explanation of the `mask` value, please refer to the
|
||||
[wiki](https://github.com/leenkx3d/leenkx/wiki/events#event-masks).
|
||||
**/
|
||||
public static function send(name: String, mask = -1) {
|
||||
var entries = get(name);
|
||||
if (entries != null) for (e in entries) if (mask == -1 || mask == e.mask ) e.onEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
Return the array of event listeners registered for events with the
|
||||
given name, or `null` if no listener is currently registered for the event.
|
||||
**/
|
||||
public static function get(name: String): Array<TEvent> {
|
||||
return events.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
Add a listener to the event with the given name and return the
|
||||
corresponding listener object. The `onEvent` callback will be called
|
||||
when a matching event is sent.
|
||||
|
||||
For an explanation of the `mask` value, please refer to the
|
||||
[wiki](https://github.com/leenkx3d/leenkx/wiki/events#event-masks).
|
||||
**/
|
||||
public static function add(name: String, onEvent: Void->Void, mask = -1): TEvent {
|
||||
var e: TEvent = { name: name, onEvent: onEvent, mask: mask };
|
||||
var entries = events.get(name);
|
||||
if (entries != null) entries.push(e);
|
||||
else events.set(name, [e]);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove _all_ listeners that listen to events with the given `name`.
|
||||
**/
|
||||
public static function remove(name: String) {
|
||||
events.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a specific listener. If the listener is not registered/added,
|
||||
this function does nothing.
|
||||
**/
|
||||
public static function removeListener(event: TEvent) {
|
||||
var entries = events.get(event.name);
|
||||
if (entries != null) {
|
||||
entries.remove(event);
|
||||
if (entries.length == 0) {
|
||||
events.remove(event.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Represents an event listener.
|
||||
|
||||
@see `leenkx.system.Event`
|
||||
**/
|
||||
typedef TEvent = {
|
||||
/** The name of the events this listener is listening to. **/
|
||||
var name: String;
|
||||
|
||||
/** The callback function that is called when a matching event is sent. **/
|
||||
var onEvent: Void->Void;
|
||||
|
||||
/** The mask of the events this listener is listening to. **/
|
||||
var mask: Int;
|
||||
}
|
||||
74
leenkx/Sources/leenkx/system/FSM.hx
Normal file
74
leenkx/Sources/leenkx/system/FSM.hx
Normal file
@ -0,0 +1,74 @@
|
||||
package leenkx.system;
|
||||
|
||||
class FSM<T> {
|
||||
final transitions = new Array<Transition<T>>();
|
||||
final tempTransitions = new Array<Transition<T>>();
|
||||
var state: Null<State<T>>;
|
||||
var entered = false;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function bindTransition(canEnter: Void -> Bool, fromState: State<T>, toState: State<T>) {
|
||||
final transition = new Transition<T>(canEnter, fromState, toState);
|
||||
transitions.push(transition);
|
||||
syncTransitions();
|
||||
}
|
||||
|
||||
public function setInitState(state: State<T>) {
|
||||
this.state = state;
|
||||
syncTransitions();
|
||||
}
|
||||
|
||||
public function update() {
|
||||
if (!entered) {
|
||||
state.onEnter();
|
||||
entered = true;
|
||||
}
|
||||
|
||||
state.onUpdate();
|
||||
|
||||
for (transition in tempTransitions) {
|
||||
if (transition.canEnter()) {
|
||||
state.onExit();
|
||||
state = transition.toState;
|
||||
entered = false;
|
||||
syncTransitions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function syncTransitions() {
|
||||
tempTransitions.resize(0);
|
||||
|
||||
for (transition in transitions) {
|
||||
if (transition.fromState == state) tempTransitions.push(transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Transition<T> {
|
||||
public final canEnter: Void -> Bool;
|
||||
public final fromState: State<T>;
|
||||
public final toState: State<T>;
|
||||
|
||||
public function new(canEnter: Void -> Bool, fromState: State<T>, toState: State<T>) {
|
||||
this.canEnter = canEnter;
|
||||
this.fromState = fromState;
|
||||
this.toState = toState;
|
||||
}
|
||||
}
|
||||
|
||||
class State<T> {
|
||||
final owner: T;
|
||||
|
||||
public function new(owner: T) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public function onEnter() {}
|
||||
|
||||
public function onUpdate() {}
|
||||
|
||||
public function onExit() {}
|
||||
}
|
||||
226
leenkx/Sources/leenkx/system/InputMap.hx
Normal file
226
leenkx/Sources/leenkx/system/InputMap.hx
Normal file
@ -0,0 +1,226 @@
|
||||
package leenkx.system;
|
||||
|
||||
import kha.FastFloat;
|
||||
import iron.system.Input;
|
||||
|
||||
class InputMap {
|
||||
|
||||
static var inputMaps = new Map<String, InputMap>();
|
||||
|
||||
public var keys(default, null) = new Array<InputMapKey>();
|
||||
public var lastKeyPressed(default, null) = "";
|
||||
|
||||
public function new() {}
|
||||
|
||||
public static function getInputMap(inputMap: String): Null<InputMap> {
|
||||
if (inputMaps.exists(inputMap)) {
|
||||
return inputMaps[inputMap];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function addInputMap(inputMap: String): InputMap {
|
||||
return inputMaps[inputMap] = new InputMap();
|
||||
}
|
||||
|
||||
public static function getInputMapKey(inputMap: String, key: String): Null<InputMapKey> {
|
||||
if (inputMaps.exists(inputMap)) {
|
||||
for (k in inputMaps[inputMap].keys) {
|
||||
if (k.key == key) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function removeInputMapKey(inputMap: String, key: String): Bool {
|
||||
if (inputMaps.exists(inputMap)) {
|
||||
var i = inputMaps[inputMap];
|
||||
|
||||
for (k in i.keys) {
|
||||
if (k.key == key) {
|
||||
return i.removeKey(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey {
|
||||
return addKey(new KeyboardKey(key, scale));
|
||||
}
|
||||
|
||||
public function addMouse(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey {
|
||||
return addKey(new MouseKey(key, scale, deadzone));
|
||||
}
|
||||
|
||||
public function addGamepad(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey {
|
||||
return addKey(new GamepadKey(key, scale, deadzone));
|
||||
}
|
||||
|
||||
public function addKey(key: InputMapKey): InputMapKey {
|
||||
keys.push(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
public function removeKey(key: InputMapKey): Bool {
|
||||
return keys.remove(key);
|
||||
}
|
||||
|
||||
public function value(): FastFloat {
|
||||
var v = 0.0;
|
||||
|
||||
for (k in keys) {
|
||||
v += k.value();
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public function started() {
|
||||
for (k in keys) {
|
||||
if (k.started()) {
|
||||
lastKeyPressed = k.key;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function released() {
|
||||
for (k in keys) {
|
||||
if (k.released()) {
|
||||
lastKeyPressed = k.key;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class InputMapKey {
|
||||
|
||||
public var key: String;
|
||||
public var scale: FastFloat;
|
||||
public var deadzone: FastFloat;
|
||||
|
||||
public function new(key: String, scale = 1.0, deadzone = 0.0) {
|
||||
this.key = key.toLowerCase();
|
||||
this.scale = scale;
|
||||
this.deadzone = deadzone;
|
||||
}
|
||||
|
||||
public function started(): Bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function released(): Bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function value(): FastFloat {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public function setIndex(index: Int) {}
|
||||
|
||||
function evalDeadzone(value: FastFloat): FastFloat {
|
||||
var v = 0.0;
|
||||
|
||||
if (value > deadzone) {
|
||||
v = value - deadzone;
|
||||
|
||||
} else if (value < -deadzone) {
|
||||
v = value + deadzone;
|
||||
}
|
||||
|
||||
return v * scale;
|
||||
}
|
||||
|
||||
function evalPressure(value: FastFloat): FastFloat {
|
||||
var v = value - deadzone;
|
||||
|
||||
if (v > 0.0) {
|
||||
v /= (1.0 - deadzone);
|
||||
|
||||
} else {
|
||||
v = 0.0;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardKey extends InputMapKey {
|
||||
|
||||
var kb = Input.getKeyboard();
|
||||
|
||||
public inline override function started() {
|
||||
return kb.started(key);
|
||||
}
|
||||
|
||||
public inline override function released() {
|
||||
return kb.released(key);
|
||||
}
|
||||
|
||||
public inline override function value(): FastFloat {
|
||||
return kb.down(key) ? scale : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
class MouseKey extends InputMapKey {
|
||||
|
||||
var m = Input.getMouse();
|
||||
|
||||
public inline override function started() {
|
||||
return m.started(key);
|
||||
}
|
||||
|
||||
public inline override function released() {
|
||||
return m.released(key);
|
||||
}
|
||||
|
||||
public override function value(): FastFloat {
|
||||
return switch (key) {
|
||||
case "movement x": evalDeadzone(m.movementX);
|
||||
case "movement y": evalDeadzone(m.movementY);
|
||||
case "wheel": evalDeadzone(m.wheelDelta);
|
||||
default: m.down(key) ? scale : 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GamepadKey extends InputMapKey {
|
||||
|
||||
var g = Input.getGamepad();
|
||||
|
||||
public inline override function started() {
|
||||
return g.started(key);
|
||||
}
|
||||
|
||||
public inline override function released() {
|
||||
return g.released(key);
|
||||
}
|
||||
|
||||
public override function value(): FastFloat {
|
||||
return switch(key) {
|
||||
case "ls movement x": evalDeadzone(g.leftStick.movementX);
|
||||
case "ls movement y": evalDeadzone(g.leftStick.movementY);
|
||||
case "rs movement x": evalDeadzone(g.rightStick.movementX);
|
||||
case "rs movement y": evalDeadzone(g.rightStick.movementY);
|
||||
case "lt pressure": evalDeadzone(evalPressure(g.down("l2")));
|
||||
case "rt pressure": evalDeadzone(evalPressure(g.down("r2")));
|
||||
default: evalDeadzone(g.down(key));
|
||||
}
|
||||
}
|
||||
|
||||
public override function setIndex(index: Int) {
|
||||
g = Input.getGamepad(index);
|
||||
}
|
||||
}
|
||||
298
leenkx/Sources/leenkx/system/Logic.hx
Normal file
298
leenkx/Sources/leenkx/system/Logic.hx
Normal file
@ -0,0 +1,298 @@
|
||||
package leenkx.system;
|
||||
|
||||
import leenkx.logicnode.*;
|
||||
|
||||
class Logic {
|
||||
|
||||
static var nodes: Array<TNode>;
|
||||
static var links: Array<TNodeLink>;
|
||||
|
||||
static var parsed_nodes: Array<String> = null;
|
||||
static var parsed_labels: Map<String, String> = null;
|
||||
static var nodeMap: Map<String, leenkx.logicnode.LogicNode>;
|
||||
|
||||
public static var packageName = "leenkx.logicnode";
|
||||
|
||||
public static function getNode(id: Int): TNode {
|
||||
for (n in nodes) if (n.id == id) return n;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getLink(id: Int): TNodeLink {
|
||||
for (l in links) if (l.id == id) return l;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getInputLink(inp: TNodeSocket): TNodeLink {
|
||||
for (l in links) {
|
||||
if (l.to_id == inp.node_id) {
|
||||
var node = getNode(inp.node_id);
|
||||
if (node.inputs.length <= l.to_socket) return null;
|
||||
if (node.inputs[l.to_socket] == inp) return l;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getOutputLinks(out: TNodeSocket): Array<TNodeLink> {
|
||||
var res: Array<TNodeLink> = [];
|
||||
for (l in links) {
|
||||
if (l.from_id == out.node_id) {
|
||||
var node = getNode(out.node_id);
|
||||
if (node.outputs.length <= l.from_socket) continue;
|
||||
if (node.outputs[l.from_socket] == out) res.push(l);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static function safesrc(s: String): String {
|
||||
return StringTools.replace(s, " ", "");
|
||||
}
|
||||
|
||||
static function node_name(node: TNode): String {
|
||||
var s = safesrc(node.name) + node.id;
|
||||
return s;
|
||||
}
|
||||
|
||||
static var tree: leenkx.logicnode.LogicTree;
|
||||
public static function parse(canvas: TNodeCanvas, onAdd = true): leenkx.logicnode.LogicTree {
|
||||
|
||||
nodes = canvas.nodes;
|
||||
links = canvas.links;
|
||||
|
||||
parsed_nodes = [];
|
||||
parsed_labels = new Map();
|
||||
nodeMap = new Map();
|
||||
var root_nodes = get_root_nodes(canvas);
|
||||
|
||||
tree = new leenkx.logicnode.LogicTree();
|
||||
if (onAdd) {
|
||||
tree.notifyOnAdd(function() {
|
||||
for (node in root_nodes) build_node(node);
|
||||
});
|
||||
}
|
||||
else {
|
||||
for (node in root_nodes) build_node(node);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
static function build_node(node: TNode): String {
|
||||
|
||||
// Get node name
|
||||
var name = node_name(node);
|
||||
|
||||
// Check if node already exists
|
||||
if (parsed_nodes.indexOf(name) != -1) {
|
||||
return name;
|
||||
}
|
||||
|
||||
parsed_nodes.push(name);
|
||||
|
||||
// Create node
|
||||
var v = createClassInstance(node.type, [tree]);
|
||||
nodeMap.set(name, v);
|
||||
|
||||
#if lnx_patch
|
||||
tree.nodes.set(name, v);
|
||||
#end
|
||||
|
||||
// Properties
|
||||
for (i in 0...5) {
|
||||
for (b in node.buttons) {
|
||||
if (b.name == "property" + i) {
|
||||
Reflect.setProperty(v, b.name, b.data[b.default_value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@:privateAccess v.preallocInputs(node.inputs.length);
|
||||
@:privateAccess v.preallocOutputs(node.outputs.length);
|
||||
|
||||
// Create inputs
|
||||
var inp_node: leenkx.logicnode.LogicNode = null;
|
||||
var inp_from = 0;
|
||||
var from_type: String;
|
||||
for (i in 0...node.inputs.length) {
|
||||
var inp = node.inputs[i];
|
||||
// Is linked - find node
|
||||
var l = getInputLink(inp);
|
||||
if (l != null) {
|
||||
var n = getNode(l.from_id);
|
||||
var socket = n.outputs[l.from_socket];
|
||||
inp_node = nodeMap.get(build_node(n));
|
||||
for (i in 0...n.outputs.length) {
|
||||
if (n.outputs[i] == socket) {
|
||||
inp_from = i;
|
||||
from_type = socket.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Not linked - create node with default values
|
||||
inp_node = build_default_node(inp);
|
||||
inp_from = 0;
|
||||
from_type = inp.type;
|
||||
}
|
||||
// Add input
|
||||
var link = LogicNode.addLink(inp_node, v, inp_from, i);
|
||||
#if lnx_patch
|
||||
link.fromType = from_type;
|
||||
link.toType = inp.type;
|
||||
link.toValue = getSocketDefaultValue(inp);
|
||||
#end
|
||||
}
|
||||
|
||||
// Create outputs
|
||||
for (i in 0...node.outputs.length) {
|
||||
var out = node.outputs[i];
|
||||
var ls = getOutputLinks(out);
|
||||
|
||||
// Linked outputs are already handled after iterating over inputs
|
||||
// above, so only unconnected outputs are handled here
|
||||
if (ls == null || ls.length == 0) {
|
||||
var link = LogicNode.addLink(v, build_default_node(out), i, 0);
|
||||
|
||||
#if lnx_patch
|
||||
link.fromType = out.type;
|
||||
link.toType = out.type;
|
||||
link.toValue = getSocketDefaultValue(out);
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static function get_root_nodes(node_group: TNodeCanvas): Array<TNode> {
|
||||
var roots: Array<TNode> = [];
|
||||
for (node in node_group.nodes) {
|
||||
// if (node.bl_idname == 'NodeUndefined') {
|
||||
// lnx.log.warn('Undefined logic nodes in ' + node_group.name)
|
||||
// return []
|
||||
// }
|
||||
var linked = false;
|
||||
for (out in node.outputs) {
|
||||
var ls = getOutputLinks(out);
|
||||
if (ls != null && ls.length > 0) {
|
||||
linked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!linked) { // Assume node with no connected outputs as roots
|
||||
roots.push(node);
|
||||
}
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
static function build_default_node(inp: TNodeSocket): leenkx.logicnode.LogicNode {
|
||||
|
||||
var v: leenkx.logicnode.LogicNode = null;
|
||||
|
||||
if (inp.type == "OBJECT") {
|
||||
v = createClassInstance("ObjectNode", [tree, inp.default_value]);
|
||||
}
|
||||
else if (inp.type == "ANIMACTION") {
|
||||
v = createClassInstance("StringNode", [tree, inp.default_value]);
|
||||
}
|
||||
else if (inp.type == "VECTOR") {
|
||||
if (inp.default_value == null) inp.default_value = [0, 0, 0]; // TODO
|
||||
v = createClassInstance("VectorNode", [tree, inp.default_value[0], inp.default_value[1], inp.default_value[2]]);
|
||||
}
|
||||
else if (inp.type == "RGBA") {
|
||||
if (inp.default_value == null) inp.default_value = [0, 0, 0]; // TODO
|
||||
v = createClassInstance("ColorNode", [tree, inp.default_value[0], inp.default_value[1], inp.default_value[2], inp.default_value[3]]);
|
||||
}
|
||||
else if (inp.type == "RGB") {
|
||||
if (inp.default_value == null) inp.default_value = [0, 0, 0]; // TODO
|
||||
v = createClassInstance("ColorNode", [tree, inp.default_value[0], inp.default_value[1], inp.default_value[2]]);
|
||||
}
|
||||
else if (inp.type == "VALUE") {
|
||||
v = createClassInstance("FloatNode", [tree, inp.default_value]);
|
||||
}
|
||||
else if (inp.type == "INT") {
|
||||
v = createClassInstance("IntegerNode", [tree, inp.default_value]);
|
||||
}
|
||||
else if (inp.type == "BOOLEAN") {
|
||||
v = createClassInstance("BooleanNode", [tree, inp.default_value]);
|
||||
}
|
||||
else if (inp.type == "STRING") {
|
||||
v = createClassInstance("StringNode", [tree, inp.default_value]);
|
||||
}
|
||||
else { // ACTION, ARRAY
|
||||
v = createClassInstance("NullNode", [tree]);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static function getSocketDefaultValue(socket: TNodeSocket): Any {
|
||||
|
||||
var v: leenkx.logicnode.LogicNode = null;
|
||||
|
||||
return switch (socket.type) {
|
||||
case "OBJECT" | "VALUE" | "INT" | "BOOLEAN" | "STRING":
|
||||
socket.default_value;
|
||||
case "VECTOR" | "RGB":
|
||||
socket.default_value == null ? [0, 0, 0] : [socket.default_value[0], socket.default_value[1], socket.default_value[2]];
|
||||
case "RGBA":
|
||||
socket.default_value == null ? [0, 0, 0, 1] : [socket.default_value[0], socket.default_value[1], socket.default_value[2], socket.default_value[3]];
|
||||
default:
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
static function createClassInstance(className: String, args: Array<Dynamic>): Dynamic {
|
||||
var cname = Type.resolveClass(packageName + "." + className);
|
||||
if (cname == null) return null;
|
||||
return Type.createInstance(cname, args);
|
||||
}
|
||||
}
|
||||
|
||||
typedef TNodeCanvas = {
|
||||
var name: String;
|
||||
var nodes: Array<TNode>;
|
||||
var links: Array<TNodeLink>;
|
||||
}
|
||||
|
||||
typedef TNode = {
|
||||
var id: Int;
|
||||
var name: String;
|
||||
var type: String;
|
||||
var x: Float;
|
||||
var y: Float;
|
||||
var inputs: Array<TNodeSocket>;
|
||||
var outputs: Array<TNodeSocket>;
|
||||
var buttons: Array<TNodeButton>;
|
||||
var color: Int;
|
||||
}
|
||||
|
||||
typedef TNodeSocket = {
|
||||
var id: Int;
|
||||
var node_id: Int;
|
||||
var name: String;
|
||||
var type: String;
|
||||
var color: Int;
|
||||
var default_value: Dynamic;
|
||||
@:optional var min: Null<Float>;
|
||||
@:optional var max: Null<Float>;
|
||||
}
|
||||
|
||||
typedef TNodeLink = {
|
||||
var id: Int;
|
||||
var from_id: Int;
|
||||
var from_socket: Int;
|
||||
var to_id: Int;
|
||||
var to_socket: Int;
|
||||
}
|
||||
|
||||
typedef TNodeButton = {
|
||||
var name: String;
|
||||
var type: String;
|
||||
@:optional var output: Null<Int>;
|
||||
@:optional var default_value: Dynamic;
|
||||
@:optional var data: Dynamic;
|
||||
@:optional var min: Null<Float>;
|
||||
@:optional var max: Null<Float>;
|
||||
}
|
||||
35
leenkx/Sources/leenkx/system/Signal.hx
Normal file
35
leenkx/Sources/leenkx/system/Signal.hx
Normal file
@ -0,0 +1,35 @@
|
||||
package leenkx.system;
|
||||
|
||||
import haxe.Constraints.Function;
|
||||
|
||||
class Signal {
|
||||
var callbacks:Array<Function> = [];
|
||||
|
||||
public function new() {
|
||||
|
||||
}
|
||||
|
||||
public function connect(callback:Function) {
|
||||
if (!callbacks.contains(callback)) callbacks.push(callback);
|
||||
}
|
||||
|
||||
public function disconnect(callback:Function) {
|
||||
if (callbacks.contains(callback)) callbacks.remove(callback);
|
||||
}
|
||||
|
||||
public function emit(...args:Any) {
|
||||
for (callback in callbacks) Reflect.callMethod(this, callback, args);
|
||||
}
|
||||
|
||||
public function getConnections():Array<Function> {
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
public function isConnected(callBack:Function):Bool {
|
||||
return callbacks.contains(callBack);
|
||||
}
|
||||
|
||||
public function isNull():Bool {
|
||||
return callbacks.length == 0;
|
||||
}
|
||||
}
|
||||
141
leenkx/Sources/leenkx/system/Starter.hx
Normal file
141
leenkx/Sources/leenkx/system/Starter.hx
Normal file
@ -0,0 +1,141 @@
|
||||
package leenkx.system;
|
||||
|
||||
import kha.WindowOptions;
|
||||
|
||||
class Starter {
|
||||
|
||||
#if lnx_loadscreen
|
||||
public static var drawLoading: kha.graphics2.Graphics->Int->Int->Void = null;
|
||||
public static var numAssets: Int;
|
||||
#end
|
||||
|
||||
public static function main(scene: String, mode: Int, resize: Bool, min: Bool, max: Bool, w: Int, h: Int, msaa: Int, vsync: Bool, getRenderPath: Void->iron.RenderPath) {
|
||||
|
||||
var tasks = 0;
|
||||
|
||||
function start() {
|
||||
if (tasks > 0) return;
|
||||
|
||||
if (leenkx.data.Config.raw == null) leenkx.data.Config.raw = {};
|
||||
var c = leenkx.data.Config.raw;
|
||||
|
||||
if (c.window_mode == null) c.window_mode = mode;
|
||||
if (c.window_resizable == null) c.window_resizable = resize;
|
||||
if (c.window_minimizable == null) c.window_minimizable = min;
|
||||
if (c.window_maximizable == null) c.window_maximizable = max;
|
||||
if (c.window_w == null) c.window_w = w;
|
||||
if (c.window_h == null) c.window_h = h;
|
||||
if (c.window_scale == null) c.window_scale = 1.0;
|
||||
if (c.window_msaa == null) c.window_msaa = msaa;
|
||||
if (c.window_vsync == null) c.window_vsync = vsync;
|
||||
|
||||
leenkx.object.Uniforms.register();
|
||||
|
||||
var windowMode = c.window_mode == 0 ? kha.WindowMode.Windowed : kha.WindowMode.Fullscreen;
|
||||
var windowFeatures = None;
|
||||
if (c.window_resizable) windowFeatures |= FeatureResizable;
|
||||
if (c.window_maximizable) windowFeatures |= FeatureMaximizable;
|
||||
if (c.window_minimizable) windowFeatures |= FeatureMinimizable;
|
||||
|
||||
#if (kha_webgl && (!lnx_legacy) && (!kha_node))
|
||||
try {
|
||||
#end
|
||||
|
||||
kha.System.start({title: Main.projectName, width: c.window_w, height: c.window_h, window: {mode: windowMode, windowFeatures: windowFeatures}, framebuffer: {samplesPerPixel: c.window_msaa, verticalSync: c.window_vsync}}, function(window: kha.Window) {
|
||||
|
||||
iron.App.init(function() {
|
||||
#if lnx_loadscreen
|
||||
function load(g: kha.graphics2.Graphics) {
|
||||
if (iron.Scene.active != null && iron.Scene.active.ready) iron.App.removeRender2D(load);
|
||||
else drawLoading(g, iron.data.Data.assetsLoaded, numAssets);
|
||||
}
|
||||
iron.App.notifyOnRender2D(load);
|
||||
#end
|
||||
iron.Scene.setActive(scene, function(object: iron.object.Object) {
|
||||
iron.RenderPath.setActive(getRenderPath());
|
||||
#if lnx_patch
|
||||
iron.Scene.getRenderPath = getRenderPath;
|
||||
#end
|
||||
#if lnx_draworder_shader
|
||||
iron.RenderPath.active.drawOrder = iron.RenderPath.DrawOrder.Shader;
|
||||
#end // else Distance
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
#if (kha_webgl && (!lnx_legacy) && (!kha_node))
|
||||
}
|
||||
catch (e: Dynamic) {
|
||||
if (!kha.SystemImpl.gl2) {
|
||||
trace("This project was not compiled with legacy shaders flag - please use WebGL 2 capable browser.");
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
#if (js && lnx_bullet)
|
||||
function loadLibAmmo(name: String) {
|
||||
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
|
||||
js.Syntax.code("(1,eval)({0})", b.toString());
|
||||
#if kha_krom
|
||||
js.Syntax.code("Ammo({print:function(s){iron.log(s);},instantiateWasm:function(imports,successCallback) {
|
||||
var wasmbin = Krom.loadBlob('ammo.wasm.wasm');
|
||||
var module = new WebAssembly.Module(wasmbin);
|
||||
var inst = new WebAssembly.Instance(module,imports);
|
||||
successCallback(inst);
|
||||
return inst.exports;
|
||||
}}).then(function(){ tasks--; start();})");
|
||||
#else
|
||||
js.Syntax.code("Ammo({print:function(s){iron.log(s);}}).then(function(){ tasks--; start();})");
|
||||
#end
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
#if (js && lnx_navigation)
|
||||
function loadLib(name: String) {
|
||||
kha.Assets.loadBlobFromPath(name, function(b: kha.Blob) {
|
||||
js.Syntax.code("(1, eval)({0})", b.toString());
|
||||
#if kha_krom
|
||||
js.Syntax.code("Recast({print:function(s){iron.log(s);},instantiateWasm:function(imports,successCallback) {
|
||||
var wasmbin = Krom.loadBlob('recast.wasm.wasm');
|
||||
var module = new WebAssembly.Module(wasmbin);
|
||||
var inst = new WebAssembly.Instance(module,imports);
|
||||
successCallback(inst);
|
||||
return inst.exports;
|
||||
}}).then(function(){ tasks--; start();})");
|
||||
#else
|
||||
js.Syntax.code("Recast({print:function(s){iron.log(s);}}).then(function(){ tasks--; start();})");
|
||||
#end
|
||||
});
|
||||
}
|
||||
#end
|
||||
|
||||
tasks = 1;
|
||||
|
||||
#if (js && lnx_bullet)
|
||||
tasks++;
|
||||
#if kha_krom
|
||||
loadLibAmmo("ammo.wasm.js");
|
||||
#else
|
||||
loadLibAmmo("ammo.js");
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (js && lnx_navigation)
|
||||
tasks++;
|
||||
#if kha_krom
|
||||
loadLib("recast.wasm.js");
|
||||
#else
|
||||
loadLib("recast.js");
|
||||
#end
|
||||
#end
|
||||
|
||||
#if (lnx_config)
|
||||
tasks++;
|
||||
leenkx.data.Config.load(function() { tasks--; start(); });
|
||||
#end
|
||||
|
||||
tasks--; start();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user