From 51a0ef6b0c939d9cd8e654a5d2f4829a51e58dd7 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sun, 23 Jul 2023 18:30:32 +0800 Subject: [PATCH] :memo: add logprint & change libs --- .gitignore | 1 + CMakeLists.txt | 1 + makefile | 11 +- src/fg_props.cpp | 31 ++ src/fg_props.h | 15 + src/nasal_builtin.cpp | 2 + std/coroutine.nas | 24 ++ std/fg_env.nas | 18 - std/lib.nas | 98 ++++-- std/props.nas | 742 ++++++++++++++++++++++++++++++++++++++++++ test/auto_crash.nas | 1 - 11 files changed, 894 insertions(+), 50 deletions(-) create mode 100644 src/fg_props.cpp create mode 100644 src/fg_props.h create mode 100644 std/coroutine.nas create mode 100644 std/props.nas diff --git a/.gitignore b/.gitignore index 0cfb1fc..5e477ca 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ nasal.exe # misc .vscode dump +fgfs.log # build dir build diff --git a/CMakeLists.txt b/CMakeLists.txt index acdace3..5b18d87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set(NASAL_OBJECT_SOURCE_FILE ${CMAKE_SOURCE_DIR}/src/ast_visitor.cpp ${CMAKE_SOURCE_DIR}/src/nasal_ast.cpp ${CMAKE_SOURCE_DIR}/src/nasal_builtin.cpp + ${CMAKE_SOURCE_DIR}/src/fg_props.cpp ${CMAKE_SOURCE_DIR}/src/nasal_codegen.cpp ${CMAKE_SOURCE_DIR}/src/nasal_dbg.cpp ${CMAKE_SOURCE_DIR}/src/nasal_err.cpp diff --git a/makefile b/makefile index 40a1c25..cc9ecfc 100644 --- a/makefile +++ b/makefile @@ -16,7 +16,8 @@ NASAL_HEADER=\ src/nasal_vm.h\ src/nasal.h\ src/optimizer.h\ - src/symbol_finder.h + src/symbol_finder.h\ + src/fg_props.h NASAL_OBJECT=\ build/nasal_err.o\ @@ -33,6 +34,7 @@ NASAL_OBJECT=\ build/nasal_misc.o\ build/nasal_gc.o\ build/nasal_builtin.o\ + build/fg_props.o\ build/nasal_vm.o\ build/nasal_dbg.o\ build/main.o @@ -82,9 +84,16 @@ build/nasal_ast.o: \ build/nasal_builtin.o: \ src/nasal.h\ src/nasal_gc.h\ + src/fg_props.h\ src/nasal_builtin.h src/nasal_builtin.cpp | build $(CXX) -std=$(STD) -c -O3 src/nasal_builtin.cpp -fno-exceptions -fPIC -o build/nasal_builtin.o -I . +build/fg_props.o: \ + src/nasal.h\ + src/nasal_gc.h\ + src/fg_props.h src/fg_props.cpp | build + $(CXX) -std=$(STD) -c -O3 src/fg_props.cpp -fno-exceptions -fPIC -o build/fg_props.o -I . + build/nasal_codegen.o: $(NASAL_HEADER) src/nasal_codegen.h src/nasal_codegen.cpp | build $(CXX) -std=$(STD) -c -O3 src/nasal_codegen.cpp -fno-exceptions -fPIC -o build/nasal_codegen.o -I . diff --git a/src/fg_props.cpp b/src/fg_props.cpp new file mode 100644 index 0000000..32f470b --- /dev/null +++ b/src/fg_props.cpp @@ -0,0 +1,31 @@ +#include "fg_props.h" + +#include + +var builtin_logprint(var* local, gc& ngc) { + var level = local[1]; + var elems = local[2]; + if (elems.type!=vm_vec) { + return nas_err("logprint", "received argument is not vector."); + } + std::ofstream out("fgfs.log", std::ios::app); + switch (static_cast(level.num())) { + case SG_LOG_BULK: out << "[LOG_BULK]"; break; + case SG_LOG_DEBUG: out << "[LOG_DEBUG]"; break; + case SG_LOG_INFO: out << "[LOG_INFO]"; break; + case SG_LOG_WARN: out << "[LOG_WARN]"; break; + case SG_LOG_ALERT: out << "[LOG_ALERT]"; break; + case SG_DEV_WARN: out << "[DEV_WARN]"; break; + case SG_DEV_ALERT: out << "[DEV_ALERT]"; break; + case SG_MANDATORY_INFO: out << "[MANDATORY_INFO]"; break; + default: + return nas_err("logprint", + "incorrect log level " + + std::to_string(level.num())); + } + for(auto& i : elems.vec().elems) { + out << i << " "; + } + out << "\n"; + return nil; +} \ No newline at end of file diff --git a/src/fg_props.h b/src/fg_props.h new file mode 100644 index 0000000..852f3a6 --- /dev/null +++ b/src/fg_props.h @@ -0,0 +1,15 @@ +#pragma once + +#include "nasal.h" +#include "nasal_gc.h" + +#define SG_LOG_BULK 1 +#define SG_LOG_DEBUG 2 +#define SG_LOG_INFO 3 +#define SG_LOG_WARN 4 +#define SG_LOG_ALERT 5 +#define SG_DEV_WARN 7 +#define SG_DEV_ALERT 8 +#define SG_MANDATORY_INFO 9 + +var builtin_logprint(var*, gc&); \ No newline at end of file diff --git a/src/nasal_builtin.cpp b/src/nasal_builtin.cpp index 4ceed8e..eef53ff 100644 --- a/src/nasal_builtin.cpp +++ b/src/nasal_builtin.cpp @@ -1,4 +1,5 @@ #include "nasal_builtin.h" +#include "fg_props.h" #include var builtin_print(var* local, gc& ngc) { @@ -1373,5 +1374,6 @@ nasal_builtin_table builtin[] = { {"__gcinfo", builtin_gcinfo}, {"__logtime", builtin_logtime}, {"__ghosttype", builtin_ghosttype}, + {"_logprint", builtin_logprint}, {nullptr, nullptr} }; \ No newline at end of file diff --git a/std/coroutine.nas b/std/coroutine.nas new file mode 100644 index 0000000..074a3ed --- /dev/null +++ b/std/coroutine.nas @@ -0,0 +1,24 @@ +# coroutine.nas +# 2023 by ValKmjolnir + +# in fact it is not multi-threaded, maybe in the future i could make it + +var create = func(function) { + return __cocreate; +} + +var resume = func(co, args...) { + return __coresume; +} + +var yield = func(args...) { + return __coyield; +} + +var status = func(co) { + return __costatus; +} + +var running = func() { + return __corun; +} diff --git a/std/fg_env.nas b/std/fg_env.nas index 209deb8..c8168d0 100644 --- a/std/fg_env.nas +++ b/std/fg_env.nas @@ -10,24 +10,6 @@ println("-------------------------------------------------------------"); println(" See help using command line argument: --fg-env-help"); println("-------------------------------------------------------------"); -# important global constants -var D2R=math.pi/180; -var FPS2KT=0.5925; -var FT2M=0.3048; -var GAL2L=3.7854; -var IN2M=0.0254; -var KG2LB=2.2046; -var KT2FPS=1.6878; -var KT2MPS=0.5144; -var L2GAL=0.2642; -var LB2KG=0.4536; -var M2FT=3.2808; -var M2IN=39.3701; -var M2NM=0.00054; -var MPS2KT=1.9438; -var NM2M=1852; -var R2D=180/math.pi; - var fg_env_cli={ "--fg-env-help":{ info:"get help", diff --git a/std/lib.nas b/std/lib.nas index 5af1204..6e513fc 100644 --- a/std/lib.nas +++ b/std/lib.nas @@ -1,6 +1,8 @@ # lib.nas # 2019 ValKmjolnir +import.std.coroutine; + # print is used to print all things in nasal, try and see how it works. # this function uses std::cout to output logs. var print = func(elems...) { @@ -219,29 +221,29 @@ var println = func(elems...) { # sort function using quick sort # not very efficient... :( -var sort=func(){ - srand(); # be aware! this causes global changes - var quick_sort_core=func(vec,left,right,cmp){ +var sort = func(){ + srand(); # be aware! this causes global changes + var quick_sort_core = func(vec, left, right, cmp) { if(left>=right) return nil; - var base=left+int(rand()*(right-left)); - (vec[left],vec[base])=(vec[base],vec[left]); - var (i,j,tmp)=(left,right,vec[left]); + var base = left+int(rand()*(right-left)); + (vec[left], vec[base]) = (vec[base], vec[left]); + var (i, j, tmp) = (left, right, vec[left]); while(i0? x:-x;}, @@ -432,6 +434,29 @@ var math = { min: func(x, y) {return x 1 and !contains(val, name)) val[name] = []; + } + if(nc > 1) append(val[name], c.getValues()); + else val[name] = c.getValues(); + } + return val; +} + +## +# Initializes property if it's still undefined. First argument +# is a property name/path. It can also be nil or an empty string, +# in which case the node itself gets initialized, rather than one +# of its children. Second argument is the default value. The third, +# optional argument is a property type (one of "STRING", "DOUBLE", +# "INT", or "BOOL"). If it is omitted, then "DOUBLE" is used for +# numbers, and STRING for everything else. Returns the property +# as props.Node. The fourth optional argument enforces a type if +# non-zero. +# +Node.initNode = func(path = nil, value = 0, type = nil, force = 0) { + var prop = me.getNode(path or "", 1); + if(prop.getType() != "NONE") value = prop.getValue(); + if(force) prop.clearValue(); + if(type == nil) prop.setValue(value); + elsif(type == "DOUBLE") prop.setDoubleValue(value); + elsif(type == "INT") prop.setIntValue(value); + elsif(type == "BOOL") prop.setBoolValue(value); + elsif(type == "STRING") prop.setValue("" ~ value); + else die("initNode(): unsupported type '" ~ type ~ "'"); + return prop; +} + +## +# Useful debugging utility. Recursively dumps the full state of a +# Node object to the console. Try binding "props.dump(props.globals)" +# to a key for a fun hack. +# +var dump = func { + if(size(arg) == 1) { var prefix = ""; var node = arg[0]; } + else { var prefix = arg[0]; var node = arg[1]; } + + var index = node.getIndex(); + var type = node.getType(); + var name = node.getName(); + var val = node.getValue(); + + if(val == nil) { val = "nil"; } + name = prefix ~ name; + if(index > 0) { name = name ~ "[" ~ index ~ "]"; } + print(name, " {", type, "} = ", val); + + # Don't recurse into aliases, lest we get stuck in a loop + if(type != "ALIAS") { + var children = node.getChildren(); + foreach(c; children) { dump(name ~ "/", c); } + } +} + +## +# Recursively copy property branch from source Node to +# destination Node. Doesn't copy aliases. Copies attributes +# if optional third argument is set and non-zero. +# +var copy = func(src, dest, attr = 0) { + var sp = src.getPath() or ""; + var dp = dest.getPath() or ""; + + # sp and dp may be equal but on different trees! + # check if dest is sub node of source + sp = split("/", sp); + dp = split("/", dp); + while (size(sp) and size(dp) and (sp[0] == dp[0])) { + sp = subvec(sp, 1); + dp = subvec(dp, 1); + } + + foreach(var c; src.getChildren()) { + var name = c.getName(); + var i = c.getIndex(); + if (i) name ~= "["~i~"]"; + if (!(!size(sp) and size(dp) and name == dp[0])) + copy(src.getNode(name), dest.getNode(name, 1), attr); + else { + logprint(DEV_WARN, "props.copy() skipping "~name~" (recursion!)"); + } + } + var type = src.getType(); + var val = src.getValue(); + if(type == "ALIAS" or type == "NONE") return; + elsif(type == "BOOL") dest.setBoolValue(val); + elsif(type == "INT" or type == "LONG") dest.setIntValue(val); + elsif(type == "FLOAT" or type == "DOUBLE") dest.setDoubleValue(val); + else dest.setValue(val); + if(attr) dest.setAttribute(src.getAttribute()); +} + +## +# Utility. Turns any ghosts it finds (either solo, or in an +# array) into Node objects. +# +var wrap = func(node) { + if(isghost(node)) { + return wrapNode(node); + } elsif(isvec(node)) { + var v = node; + var n = size(v); + for(var i=0; i property branch according to the rules +# set out in $FG_ROOT/Docs/README.conditions into a Condition object. +# The 'test' method of the returend object can be used to evaluate +# the condition. +# The function returns nil on error. +# +var compileCondition = func(p) { + if(p == nil) return nil; + if(!isa(p, Node)) p = props.globals.getNode(p); + return _createCondition(p._g); +} + +## +# Evaluates a property branch according to the rules +# set out in $FG_ROOT/Docs/README.conditions. Undefined conditions +# and a nil argument are "true". The function dumps the condition +# branch and returns nil on error. +# +var condition = func(p) { + if(p == nil) return 1; + if(!isa(p, Node)) p = props.globals.getNode(p); + return _cond_and(p) +} + +var _cond_and = func(p) { + foreach(var c; p.getChildren()) + if(!_cond(c)) return 0; + return 1; +} + +var _cond_or = func(p) { + foreach(var c; p.getChildren()) + if(_cond(c)) return 1; + return 0; +} + +var _cond = func(p) { + var n = p.getName(); + if(n == "or") return _cond_or(p); + if(n == "and") return _cond_and(p); + if(n == "not") return !_cond_and(p); + if(n == "equals") return _cond_cmp(p, 0); + if(n == "not-equals") return !_cond_cmp(p, 0); + if(n == "less-than") return _cond_cmp(p, -1); + if(n == "greater-than") return _cond_cmp(p, 1); + if(n == "less-than-equals") return !_cond_cmp(p, 1); + if(n == "greater-than-equals") return !_cond_cmp(p, -1); + if(n == "property") return !!getprop(p.getValue()); + logprint(LOG_ALERT, "condition: invalid operator ", n); + dump(p); + return nil; +} + +var _cond_cmp = func(p, op) { + var left = p.getChild("property", 0, 0); + if(left != nil) { left = getprop(left.getValue()); } + else { + logprint(LOG_ALERT, "condition: no left value"); + dump(p); + return nil; + } + var right = p.getChild("property", 1, 0); + if(right != nil) { right = getprop(right.getValue()); } + else { + right = p.getChild("value", 0, 0); + if(right != nil) { right = right.getValue(); } + else { + logprint(LOG_ALERT, "condition: no right value"); + dump(p); + return nil; + } + } + + if (left == nil) left = 0.0; + if (right == nil) right = 0.0; + + if(op < 0) return left < right; + if(op > 0) return left > right; + return left == right; +} + +## +# Runs as described in $FG_ROOT/Docs/README.commands using +# a given module by default, and returns 1 if fgcommand() succeeded, +# or 0 otherwise. The module name won't override a defined +# in the binding. +# +var runBinding = func(node, module = nil) { + if(module != nil and node.getNode("module") == nil) + node.getNode("module", 1).setValue(module); + var cmd = node.getNode("command", 1).getValue() or "null"; + condition(node.getNode("condition")) ? fgcommand(cmd, node) : 0; +} + + #--------------------------------------------------------------------------- + # Property / object update manager + # + # - Manage updates when a value has changed more than a predetermined amount. + # This class is designed to make updating displays (e.g. canvas), or + # performing actions based on a property (or value in a hash) changing + # by more than the preset amount. + # This can make a significant improvement to performance compared to simply + # redrawing a canvas in an update loop. + # - Author : Richard Harrison (rjh@zaretto.com) + #---------------------------------------------------------------------------*/ + +#example usage: +# this is using the hashlist (which works well with an Emesary notification) +# basically when the method is called it will call each section (in the lambda) +# when the value changes by more than the amount specified as the second parameter. +# It is possible to reference multiple elements from the hashlist in each FromHashList; if either +# one changes then it will result in the lambda being called. +# +# obj.update_items = [ +# UpdateManager.FromHashList(["VV_x","VV_y"], 0.01, func(val) +# { +# obj.VV.setTranslation (val.VV_x, val.VV_y + pitch_offset); +# }), +# UpdateManager.FromHashList(["pitch","roll"], 0.025, func(hdp) +# { +# obj.ladder.setTranslation (0.0, hdp.pitch * pitch_factor+pitch_offset); +# obj.ladder.setCenter (118,830 - hdp.pitch * pitch_factor-pitch_offset); +# obj.ladder.setRotation (-hdp.roll_rad); +# obj.roll_pointer.setRotation (hdp.roll_rad); +# }), +# props.UpdateManager.FromProperty("velocities/airspeed-kt", 0.01, func(val) +# { +# obj.ias_range.setTranslation(0, val * ias_range_factor); +# }), +# props.UpdateManager.FromPropertyHashList(["orientation/alpha-indicated-deg", "orientation/side-slip-deg"], 0.1, func(val) +# { +# obj.VV_x = val.property["orientation/side-slip-deg"].getValue()*10; # adjust for view +# obj.VV_y = val.property["orientation/alpha-indicated-deg"].getValue()*10; # adjust for view +# obj.VV.setTranslation (obj.VV_x, obj.VV_y); +# }), +# ] +# +#==== the update loop then becomes ====== +# +# foreach(var update_item; me.update_items) +# { +# # hdp is a data provider that can be used as the hashlist for the property +# # update from hash methods. +# update_item.update(hdp); +# } +# +var UpdateManager = +{ + _updateProperty : func(_property) + { + }, + FromProperty : func(_propname, _delta, _changed_method) + { + var obj = {parents : [UpdateManager] }; + obj.propname = _propname; + obj.property = props.globals.getNode(_propname); + obj.delta = _delta; + obj.curval = obj.property.getValue(); + obj.lastval = obj.curval; + obj.changed = _changed_method; + obj.update = func(obj) + { + me.curval = me.property.getValue(); + if (me.curval != nil) + { + me.localType = me.property.getType(); + if (me.localType == "INT" or me.localType == "LONG" or me.localType == "FLOAT" or me.localType == "DOUBLE") + { + if(me.lastval == nil or math.abs(me.lastval - me.curval) >= me.delta) + { + me.lastval = me.curval; + me.changed(me.curval); + } + } + else if(me.lastval == nil or me.lastval != me.curval) + { + me.lastval = me.curval; + me.changed(me.curval); + } + } + }; + obj.update(obj); + return obj; + }, + + IsNumeric : func(hashkey) + { + me.localType = me.property[hashkey].getType(); + if (me.localType == "UNSPECIFIED") { + print("UpdateManager: warning ",hashkey," is ",me.localType, " excluding from update"); + me.property[hashkey] = nil; + } + if (me.localType == "INT" or me.localType == "LONG" or me.localType == "FLOAT" or me.localType == "DOUBLE") + return 1; + else + return 0; + }, + + FromPropertyHashList : func(_keylist, _delta, _changed_method) + { + var obj = {parents : [UpdateManager] }; + obj.hashkeylist = _keylist; + obj.delta = _delta; + obj.lastval = {}; + obj.hashkey = nil; + obj.changed = _changed_method; + obj.needs_update = 0; + obj.property = {}; + obj.is_numeric = {}; + foreach (hashkey; obj.hashkeylist) { + obj.property[hashkey] = props.globals.getNode(hashkey); + obj.lastval[hashkey] = nil; +# var ty = obj.property[hashkey].getType(); +# if (ty == "INT" or ty == "LONG" or ty == "FLOAT" or ty == "DOUBLE") { +# obj.is_numeric[hashkey] = 1; +# } else +# obj.is_numeric[hashkey] = 0; +#print("create: ", hashkey," ", ty, " isnum=",obj.is_numeric[hashkey]); +# if (ty == "UNSPECIFIED") +# print("UpdateManager: warning ",hashkey," is ",ty); + } + obj.update = func(obj) + { + if (me.lastval == nil) + me.needs_update = 1; + else { + me.needs_update = 0; + + foreach (hashkey; me.hashkeylist) { + if (me.property[hashkey] != nil) { + me.valIsNumeric = me.IsNumeric(hashkey); + + if (me.lastval[hashkey] == nil + or (me.valIsNumeric and (math.abs(me.lastval[hashkey] - me.property[hashkey].getValue()) >= me.delta)) + or (!me.valIsNumeric and (me.lastval[hashkey] != me.property[hashkey].getValue()))) { + me.needs_update = 1; + break; + } + } + } + } + if (me.needs_update) { + me.changed(me); + foreach (hashkey; me.hashkeylist) { + me.lastval[hashkey] = me.property[hashkey].getValue(); + } + } + } + ; + return obj; + }, + FromHashValue : func(_key, _delta, _changed_method) + { + var obj = {parents : [UpdateManager] }; + obj.hashkey = _key; + obj.delta = _delta; + obj.isnum = _delta != nil; + obj.curval = nil; + obj.lastval = nil; + obj.changed = _changed_method; + obj.update = func(obj) + { + me.curval = obj[me.hashkey]; + if (me.curval != nil) { + if (me.isnum) { + me.curval = num(me.curval); + if (me.lastval == nil or math.abs(me.lastval - me.curval) >= me.delta) { + me.lastval = me.curval; + me.changed(me.curval); + } + } else { + if (me.lastval == nil or me.lastval != me.curval) { + me.lastval = me.curval; + me.changed(me.curval); + } + } + } + } + ; + return obj; + }, + FromHashList : func(_keylist, _delta, _changed_method) + { + var obj = {parents : [UpdateManager] }; + obj.hashkeylist = _keylist; + obj.delta = _delta; + obj.lastval = {}; + obj.hashkey = nil; + obj.changed = _changed_method; + obj.needs_update = 0; + obj.isnum = _delta != nil; + obj.update = func(obj) + { + if (me.lastval == nil) + me.needs_update = 1; + else + me.needs_update = 0; + + if (obj != nil or me.lastval == nil) { + foreach (hashkey; me.hashkeylist) { + if (me.isnum) { + if (me.lastval[hashkey] == nil or math.abs(me.lastval[hashkey] - obj[hashkey]) >= me.delta) { + me.needs_update = 1; + break; + } + } elsif (me.lastval[hashkey] == nil or me.lastval[hashkey] != obj[hashkey]) { + me.needs_update = 1; + break; + } + } + } + if (me.needs_update) { + me.changed(obj); + foreach (hashkey; me.hashkeylist) { + me.lastval[hashkey] = obj[hashkey]; + } + } + }; + return obj; + }, +}; diff --git a/test/auto_crash.nas b/test/auto_crash.nas index ce77221..b277c4b 100644 --- a/test/auto_crash.nas +++ b/test/auto_crash.nas @@ -4,7 +4,6 @@ import.std.fg_env; var props = fg_env.props; var geodinfo = fg_env.geodinfo; var maketimer = fg_env.maketimer; -var D2R = fg_env.D2R; var simulation = fg_env.simulation; var dt=0.01;