Moved most script logic to ScriptController

This commit is contained in:
Scott Wadden 2022-02-08 17:06:13 -04:00
parent f6bea8cae3
commit c87ce39651
No known key found for this signature in database
GPG Key ID: 281836B32D24AA27
42 changed files with 1197 additions and 1175 deletions

View File

@ -1,4 +1,5 @@
# begin Nimble config (version 1)
--experimental:overloadableEnums
when fileExists("nimble.paths"):
include "nimble.paths"
# end Nimble config

View File

@ -4,6 +4,7 @@
--tlsEmulation:off
--threadAnalysis:off
--define:nimPreviewHashRef
--experimental:dynamicBindSym
#--define:nimLeakDetector
--define:enuHacks

View File

@ -2,32 +2,13 @@ import std / [strutils, os, tables]
import pkg / [model_citizen, print]
import pkg/godot except print
import godotapi / [node, spatial]
import models, models / scripts, world / [bot_node, build_node], engine / contexts
import models, world / [bot_node, build_node]
type
UnitController* = object
NodeController* = object
let state = GameState.active
proc change_code(self: Unit, code: string, retry = true) =
self.transform.value = self.start_transform
if not retry_failures:
state.paused = false
self.reset()
state.console.show_errors.value = false
if code.strip == "" and file_exists(self.script_file):
remove_file self.script_file
remove_module self.script_file
elif code.strip != "":
write_file(self.script_file, code)
if self.script_ctx.is_nil:
self.script_ctx = ScriptCtx.init
unit_ctxs[self.script_ctx.engine] = self
self.script_ctx.script = self.script_file
if retry:
self.script_ctx.retry_on_nil = true
self.load_script()
proc remove_from_scene(unit: Unit) =
unit.reset()
if unit == previous_build: previous_build = nil
@ -39,14 +20,11 @@ proc remove_from_scene(unit: Unit) =
field.untrack_all
if unit of Build: Build(unit).untrack_all
elif unit of Bot: Bot(unit).untrack_all
if not unit.script_ctx.is_nil and not unit.script_ctx.engine.is_nil:
let e = unit.script_ctx.engine
unit_ctxs[e] = nil
ctxs[e] = nil
unit.script_ctx.engine.callback = nil
unit.script_ctx.callback = nil
if not unit.clone_of:
remove_file unit.script_file
remove_module unit.script_file
# TODO?
# remove_module unit.script_file
remove_dir unit.data_dir
for child in unit.units:
child.remove_from_scene()
@ -101,12 +79,6 @@ proc find_nested_changes(parent: Change[Unit]) =
change.item.add_to_scene()
elif Removed in change.changes:
change.item.remove_from_scene()
if Added in change.changes and change of Change[string] and parent.field_name == "code":
let change = Change[string](change)
parent.item.change_code(change.item)
if Touched in change.changes and change of Change[string] and parent.field_name == "code":
let change = Change[string](change)
parent.item.change_code(change.item, false)
if change of Change[ModelFlags]:
let change = Change[ModelFlags](change)
if change.item == Global:
@ -115,7 +87,7 @@ proc find_nested_changes(parent: Change[Unit]) =
elif Removed in change.changes:
parent.item.set_global(false)
proc watch*(f: UnitController, state: GameState) =
proc watch*(f: NodeController, state: GameState) =
state.units.changes:
if added():
change.item.add_to_scene()
@ -124,14 +96,6 @@ proc watch*(f: UnitController, state: GameState) =
elif removed():
change.item.remove_from_scene()
proc reload_all*(_: type UnitController) =
ctxs.clear()
unit_ctxs.clear()
reset_interpreter()
walk_tree state.units.value, proc(unit: Unit) =
unit.script_ctx = nil
unit.code.touch unit.code.value
proc init*(_: type UnitController): UnitController =
result = UnitController()
proc init*(_: type NodeController): NodeController =
result = NodeController()
result.watch state

View File

@ -0,0 +1,373 @@
import std / [macros, sugar, sets, os, strutils, times, monotimes, sequtils, importutils, tables]
import pkg / [print, model_citizen]
import pkg / compiler / vm except get_int
import pkg / compiler / ast except new_node
import pkg / compiler / [vmdef, options, lineinfos, astalgo, renderer, msgs]
import core, models / [types, states, bots, units], libs / [interpreters, eval]
include script_controllers/converters
type ScriptController* = ref object
interpreter: Interpreter
module_names: HashSet[string]
active_unit: Unit
unit_map: Table[PNode, Unit]
node_map: Table[Unit, PNode]
const ADVANCE_STEP* = 0.5.seconds
let state = GameState.active
var failed: seq[tuple[unit: Unit, ex: ref VMQuit]]
proc map_unit(self: ScriptController, unit: Unit, pnode: PNode) =
self.unit_map[pnode] = unit
self.node_map[unit] = pnode
proc unmap_unit(self: ScriptController, unit: Unit) =
if unit in self.node_map:
self.unit_map.del self.node_map[unit]
self.node_map.del unit
proc get_unit(self: ScriptController, a: VmArgs, pos: int): Unit =
let pnode = a.get_node(pos)
self.unit_map[pnode]
proc begin_turn(self: ScriptController, unit: Unit, direction: Vector3, degrees: float, move_mode: int): string =
var degrees = degrees
var direction = direction
let ctx = self.active_unit.script_ctx
if degrees < 0:
degrees = degrees * -1
direction = direction * -1
ctx.callback = unit.on_begin_turn(direction, degrees, 1)
if not ctx.callback.is_nil:
ctx.pause()
proc begin_move(self: ScriptController, unit: Unit, direction: Vector3, steps: float, move_mode: int) =
var steps = steps
var direction = direction
let ctx = self.active_unit.script_ctx
if steps < 0:
steps = steps * -1
direction = direction * -1
ctx.callback = unit.on_begin_move(direction, steps, 1)
if not ctx.callback.is_nil:
ctx.pause()
proc register_active(self: ScriptController, pnode: PNode) =
self.map_unit(self.active_unit, pnode)
proc new_instance(self: ScriptController, src: Unit, dest: PNode) =
var clone = src.clone(self.active_unit)
clone.script_ctx = ScriptCtx(timer: MonoTime.high, interpreter: self.interpreter,
module_name: src.script_ctx.module_name)
self.map_unit(clone, dest)
self.active_unit.units.add(clone)
let active = self.active_unit
self.active_unit = clone
defer:
self.active_unit = active
discard clone.script_ctx.call_proc("run_script", dest)
proc echo_console(msg: string) =
echo msg
proc action_running(self: Unit): bool =
self.script_ctx.action_running
proc `action_running=`(self: Unit, value: bool) =
if value:
self.script_ctx.timer = get_mono_time() + ADVANCE_STEP
else:
self.script_ctx.timer = MonoTime.high
self.script_ctx.action_running = value
proc yield_script(self: ScriptController, unit: Unit) =
let ctx = unit.script_ctx
ctx.callback = ctx.saved_callback
ctx.saved_callback = nil
ctx.pause()
macro bind_procs(self: ScriptController, proc_refs: varargs[typed]): untyped =
result = new_stmt_list()
result.add quote do:
let script_controller {.inject.} = `self`
for proc_ref in proc_refs:
let
proc_impl = proc_ref.get_impl
proc_name = proc_impl[0].str_val
return_node = proc_impl[3][0]
arg_nodes = proc_impl[3][1..^1]
let args = collect:
var pos = -1
for ident_def in arg_nodes:
let typ = ident_def[1].str_val
if typ == $ScriptController.type:
ident"script_controller"
elif typ == "VmArgs":
ident"a"
elif typ in ["Unit", "Bot", "Build"]:
let getter = "get_" & typ
pos.inc
new_call(bind_sym(getter), ident"script_controller", ident"a", new_lit(pos))
else:
let getter = "get_" & typ
pos.inc
new_call(bind_sym(getter), ident"a", new_lit(pos))
var call = new_call(proc_ref, args)
if return_node.kind == nnkSym:
call = new_call(bind_sym"set_result", ident"a", new_call(bind_sym"to_node", call))
result.add quote do:
mixin implement_routine
`self`.interpreter.implement_routine "*", "base_api", `proc_name`, proc(a {.inject.}: VmArgs) {.gcsafe.} =
`call`
proc script_error(self: ScriptController, unit: Unit, e: ref VMQuit) =
echo "error: ", e.msg
proc advance_unit(self: ScriptController, unit: Unit, delta: float) =
let ctx = unit.script_ctx
if ctx and ctx.running:
let now = get_mono_time()
if unit of Build:
let unit = Build(unit)
unit.voxels_remaining_this_frame += unit.voxels_per_frame
var resume_script = true
try:
while resume_script and not state.paused:
resume_script = false
if ctx.callback == nil or (not ctx.callback(delta)):
ctx.timer = MonoTime.high
ctx.action_running = false
assert self.active_unit.is_nil
self.active_unit = unit
ctx.running = ctx.resume()
if unit of Build:
let unit = Build(unit)
if unit.voxels_per_frame > 0 and ctx.running and unit.voxels_remaining_this_frame >= 1:
resume_script = true
elif now >= ctx.timer:
ctx.timer = now + ADVANCE_STEP
ctx.saved_callback = ctx.callback
ctx.callback = nil
# TODO
self.active_unit = unit
discard ctx.resume()
except VMQuit as e:
self.script_error(unit, e)
finally:
self.active_unit = nil
proc load_script(self: ScriptController, unit: Unit) =
let ctx = unit.script_ctx
self.active_unit = unit
try:
if not state.paused:
let module_name = ctx.script.split_file.name
var others = self.module_names
self.module_names.incl module_name
others.excl module_name
let imports = if others.card > 0:
"import " & others.to_seq.join(", ")
else:
""
let code = unit.code_template(imports)
ctx.load(state.config.script_dir, ctx.script, code, state.config.lib_dir)
# if not initialized:
# with ctx.engine:
# expose "yield_script", proc(a: VmArgs):bool =
# active_engine().callback = active_engine().saved_callback
# active_engine().saved_callback = nil
# true
#
# expose "begin_move", a => self.begin_move(get_vec3(a, 0), get_float(a, 1), get_int(a, 2).int)
# expose "begin_turn", a => self.begin_turn(get_vec3(a, 0), get_float(a, 1), get_int(a, 2).int)
# expose "echo_console", proc(a: VmArgs): bool =
# let msg = a.get_string(0)
# echo msg
# state.console.log += msg
# expose "create_new", a => self.create_new()
# expose "collision", proc(a: VmArgs): bool =
# for collision in self.collisions:
# if collision.model == state.player:
# a.set_result(collision.normal.snapped(vec3(1, 1, 1)).to_node)
# return false
# a.set_result(vec3().to_node)
# false
# expose "sleep", proc(a: VmArgs): bool =
# self.load_vars()
# let seconds = get_float(a, 0)
# var duration = 0.0
# active_engine().callback = proc(delta: float): bool =
# duration += delta
# return duration < seconds
# true
# expose "quit", proc(a: VmArgs): bool =
# let e = active_engine()
# e.exit_code = some(a.get_int(0).int)
# e.pause()
# e.running = false
# result = false
# expose "set_global", proc(a: VmArgs): bool =
# let global = a.get_bool(0)
# if global:
# self.flags += Global
# else:
# self.flags -= Global
# false
# expose "get_global", proc(a: VmArgs): bool =
# a.set_result((Global in self.flags).to_node)
# false
# expose "get_position", proc(a: VmArgs): bool =
# let n = self.to_global(vec3(0, 0, 0)).to_node
# a.set_result(n)
# return false
# expose "set_position", proc(a: VmArgs): bool =
# let v = a.get_vec3(0)
# self.transform.origin = v
# false
# expose "get_rotation", proc(a: VmArgs): bool =
# # TODO: fix this
# proc nm(f: float): float =
# if f.is_equal_approx(0):
# return 0
# elif f < 0:
# return f + (2 * PI)
# else:
# return f
#
# proc nm(v: Vector3): Vector3 =
# vec3(v.x.nm, v.y.nm, v.z.nm)
#
# let e = self.transform.basis.get_euler
#
# let n = e.nm
# let v = vec3(nm(n.x).rad_to_deg, nm(n.y).rad_to_deg, nm(n.z).rad_to_deg)
# let m = if v.z > 0: 1.0 else: -1.0
# let v2 = vec3(0.0, (v.x - v.y) * m, 0.0)
# a.set_result(v2.to_node)
# return false
# self.on_script_loaded()
if not state.paused:
ctx.running = ctx.run()
#self.update_running_state ctx.run()
except VMQuit as e:
#if retry_failures:
ctx.running = false
#failed.add (self, e)
#else:
# discard
#self.error(e)
finally:
self.active_unit = nil
proc remove_module*(self: ScriptController, file_name: string) =
self.module_names.excl file_name.split_file.name
proc change_code(self: ScriptController, unit: Unit, code: string) =
# if not retry_failures:
# state.paused = false
unit.reset()
state.console.show_errors.value = false
if code.strip == "" and file_exists(unit.script_file):
remove_file unit.script_file
self.remove_module unit.script_file
elif code.strip != "":
write_file(unit.script_file, code)
if unit.script_ctx.is_nil:
unit.script_ctx = ScriptCtx(timer: MonoTime.high, interpreter: self.interpreter)
#unit_ctxs[unit.script_ctx.engine] = unit
unit.script_ctx.script = unit.script_file
self.load_script(unit)
proc watch_code(self: ScriptController, unit: Unit) =
unit.code.changes:
if added or touched:
self.change_code(unit, change.item)
proc watch_units(self: ScriptController, units: ZenSeq[Unit]) =
units.changes:
let unit = change.item
if added:
unit.frame_delta.changes:
if touched:
self.advance_unit(unit, change.item)
self.watch_code unit
self.watch_units unit.units
if not unit.clone_of and file_exists(unit.script_file):
unit.code.value = read_file(unit.script_file)
if removed:
self.unmap_unit(unit)
proc reload_all*(self: ScriptController) =
# TODO?
# reset_interpreter()
walk_tree state.units.value, proc(unit: Unit) =
unit.script_ctx = nil
unit.code.touch unit.code.value
proc init*(_: type ScriptController): ScriptController =
private_access ScriptCtx
let interpreter = Interpreter.init(state.config.script_dir, state.config.lib_dir)
let controller = ScriptController(interpreter: interpreter)
interpreter.register_error_hook proc(config, info, msg, severity: auto) {.gcsafe.} =
let ctx = controller.active_unit.script_ctx
if severity == Severity.Error and config.error_counter >= config.error_max:
let file_name = if info.file_index.int >= 0:
config.m.file_infos[info.file_index.int].full_path.string
else:
"???"
let msg = &"{file_name}({int info.line},{int info.col}): {msg}"
echo "error: ", msg, " from ", ctx.file_name
ctx.errors.add (msg, info)
#ctx.exit_code = some(99)
raise (ref VMQuit)(info: info, msg: msg)
interpreter.register_enter_hook proc(c, pc, tos, instr: auto) =
assert controller
assert controller.active_unit
assert controller.active_unit.script_ctx
let ctx = controller.active_unit.script_ctx
let info = c.debug[pc]
if ctx.previous_line != info:
let config = interpreter.config
if info.file_index.int >= 0 and info.file_index.int < config.m.file_infos.len:
let file_name = config.m.file_infos[info.file_index.int].full_path.string
if file_name == ctx.file_name:
if ctx.line_changed != nil:
ctx.line_changed(info, ctx.previous_line)
(ctx.previous_line, ctx.current_line) = (ctx.current_line, info)
if ctx.pause_requested:
ctx.pause_requested = false
ctx.ctx = c
ctx.pc = pc
ctx.tos = tos
raise new_exception(VMPause, "vm paused")
result = controller
result.watch_units state.units
result.bind_procs begin_turn, begin_move, register_active, echo_console, new_instance,
action_running, `action_running=`, yield_script
when is_main_module:
state.config.lib_dir = current_source_path().parent_dir / ".." / ".." / "vmlib"
var b = Bot.init
let c = ScriptController.init

View File

@ -0,0 +1,41 @@
proc get_int(a: VmArgs; i: Natural): int = int vm.get_int(a, i)
proc get_pnode(a: VmArgs, pos: int): PNode {.inline.} = a.get_node(pos)
proc get_vector3(a: VmArgs, pos: int): Vector3 =
proc float_val(node: PNode, name: string): float =
assert node.kind == nkExprColonExpr
assert node.sons[0].sym.kind == skField
assert node.sons[0].sym.name.s == name
result = node.sons[1].float_val
let
fields = a.get_node(pos).sons
x = float_val(fields[1], "x")
y = float_val(fields[2], "y")
z = float_val(fields[3], "z")
result = vec3(x, y, z)
# adapted from https://github.com/h0lley/embeddedNimScript/blob/6101fb37d4bd3f947db86bac96f53b35d507736a/embeddedNims/enims.nim#L31
proc to_node(val: int): PNode =
echo "converting int"
new_int_node(nkIntLit, val)
proc to_node(val: float): PNode = new_float_node(nkFloatLit, val)
proc to_node(val: string): PNode = new_str_node(nkStrLit, val)
proc to_node(val: bool): BiggestInt = BiggestInt(val)
proc to_node(val: enum): PNode = val.ord.to_node
proc to_node(list: open_array[int|float|string|bool|enum]): PNode =
result = nkBracket.new_node
result.sons.initialize(list.len)
for i in 0..list.high: result.sons[i] = list[i].to_node()
proc to_node(tree: tuple|object): PNode =
result = nkPar.new_tree
for field in tree.fields:
result.sons.add(field.to_node)
proc to_node(tree: ref tuple|ref object): PNode =
result = nkPar.new_tree
if tree.is_nil: return result
for field in tree.fields:
result.sons.add(field.to_node)

View File

@ -113,7 +113,7 @@ proc trunc*(self: Vector3): Vector3 {.inline.} =
proc inverse_normalized*(self: Vector3): Vector3 {.inline.} =
(self - vec3(self.length, self.length, self.length)) * -1
proc first*[T](arr: openArray[T], test: proc(x: T): bool): Option[T] =
proc first*[T](arr: open_array[T], test: proc(x: T): bool): Option[T] =
for item in arr:
if test(item):
return some(item)

View File

@ -1,247 +0,0 @@
import std / [monotimes, os, hashes, sets, strutils, sugar, math]
import pkg / godot except print
import pkg / print
import godotapi / [node]
import core, engine/engine, models, globals
export engine
let state = GameState.active
let config = state.config
var
module_names: HashSet[string]
failed: seq[tuple[unit: Unit, ex: ref VMQuit]]
retry_failures* = false
proc load_script*(self: Unit, script = "")
proc create_new(self: Unit): bool
proc destroy*(ctx: ScriptCtx) =
if ctx.engine in ctxs:
ctxs.del(ctx.engine)
proc init*(_: type ScriptCtx): ScriptCtx =
let e = Engine.init()
result = ScriptCtx(engine: e, timer: MonoTime.high)
ctxs[e] = result
proc is_script_loadable*(self: Unit): bool =
let ctx = self.script_ctx
ctx.script != "none" and ctx.script.file_exists
proc error*(self: Unit, ex: ref VMQuit) =
var ctx = self.script_ctx
ctx.engine.running = false
when defined(enu_simulate):
raise ex
else:
let (root, _) = self.find_root(true)
ctx = root.script_ctx
if ctx.retry_on_nil and ("attempt to access a nil address" in ex.msg):
root.code.touch root.code.value
else:
state.err "Exception executing " & self.script_ctx.script
state.err ex.msg
state.console.show_errors.value = true
proc retry_failed_scripts* =
var prev_failed = failed
failed = @[]
for f in prev_failed:
echo "retrying: ", f.unit.script_ctx.script
f.unit.load_script()
if failed.len > 0 and prev_failed.len == failed.len:
prev_failed = failed
for f in prev_failed:
f.unit.error(f.ex)
proc update_running_state(self: Unit, running: bool) =
self.script_ctx.engine.running = running
if not running:
self.load_vars()
state.debug(self.script_ctx.script & " done.")
proc remove_module*(file_name: string) =
module_names.excl file_name.split_file.name
proc advance*(self: Unit, delta: float64) =
let now = get_mono_time()
let c = self.script_ctx
let e = c.engine
if self of Build:
let self = Build(self)
self.voxels_remaining_this_frame += self.voxels_per_frame
var resume_script = true
try:
while resume_script and not state.paused:
resume_script = false
if e.callback == nil or (not e.callback(delta)):
c.timer = MonoTime.high
discard e.call_proc("set_action_running", e.module_name, false)
self.update_running_state e.resume()
if self of Build:
let self = Build(self)
if self.voxels_per_frame > 0 and e.running and self.voxels_remaining_this_frame >= 1:
resume_script = true
elif now >= c.timer:
c.timer = now + ADVANCE_STEP
e.saved_callback = e.callback
e.callback = nil
discard e.resume()
except VMQuit as e:
self.error(e)
proc begin_move(self: Unit, direction: Vector3, steps: float, move_mode: int): bool =
self.load_vars()
var steps = steps
var direction = direction
if steps < 0:
steps = steps * -1
direction = direction * -1
active_engine().callback = self.on_begin_move(direction, steps, move_mode)
result = not active_engine().callback.is_nil
proc begin_turn(self: Unit, direction: Vector3, degrees: float, move_mode: int): bool =
self.load_vars()
var degrees = degrees
var direction = direction
if degrees < 0:
degrees = degrees * -1
direction = direction * -1
active_engine().callback = self.on_begin_turn(direction, degrees, move_mode)
result = not active_engine().callback.is_nil
proc load_script*(self: Unit, script = "") =
let ctx = self.script_ctx
if script != "":
ctx.script = script
ctx.engine.callback = nil
try:
if not self.is_script_loadable:
return
if not state.paused:
let module_name = ctx.script.split_file.name
var others = module_names
if not ctx.is_clone:
module_names.incl module_name
others.excl module_name
let imports = if others.card > 0:
"import " & others.to_seq.join(", ")
else:
""
let code = self.code_template(imports)
let initialized = ctx.engine.initialized
let suffex = if ctx.is_clone: "_clone" & self.id else: ""
ctx.load_vars = proc() = self.load_vars()
ctx.engine.load(config.script_dir, ctx.script, code, config.lib_dir, suffex)
if not initialized:
with ctx.engine:
expose "yield_script", proc(a: VmArgs):bool =
active_engine().callback = active_engine().saved_callback
active_engine().saved_callback = nil
true
expose "begin_move", a => self.begin_move(get_vec3(a, 0), get_float(a, 1), get_int(a, 2).int)
expose "begin_turn", a => self.begin_turn(get_vec3(a, 0), get_float(a, 1), get_int(a, 2).int)
expose "echo_console", proc(a: VmArgs): bool =
let msg = a.get_string(0)
echo msg
state.console.log += msg
expose "create_new", a => self.create_new()
expose "collision", proc(a: VmArgs): bool =
for collision in self.collisions:
if collision.model == state.player:
a.set_result(collision.normal.snapped(vec3(1, 1, 1)).to_node)
return false
a.set_result(vec3().to_node)
false
expose "sleep", proc(a: VmArgs): bool =
self.load_vars()
let seconds = get_float(a, 0)
var duration = 0.0
active_engine().callback = proc(delta: float): bool =
duration += delta
return duration < seconds
true
expose "quit", proc(a: VmArgs): bool =
let e = active_engine()
e.exit_code = some(a.get_int(0).int)
e.pause()
e.running = false
result = false
expose "set_global", proc(a: VmArgs): bool =
let global = a.get_bool(0)
if global:
self.flags += Global
else:
self.flags -= Global
false
expose "get_global", proc(a: VmArgs): bool =
a.set_result((Global in self.flags).to_node)
false
expose "get_position", proc(a: VmArgs): bool =
let n = self.to_global(vec3(0, 0, 0)).to_node
a.set_result(n)
return false
expose "set_position", proc(a: VmArgs): bool =
let v = a.get_vec3(0)
self.transform.origin = v
false
expose "get_rotation", proc(a: VmArgs): bool =
# TODO: fix this
proc nm(f: float): float =
if f.is_equal_approx(0):
return 0
elif f < 0:
return f + (2 * PI)
else:
return f
proc nm(v: Vector3): Vector3 =
vec3(v.x.nm, v.y.nm, v.z.nm)
let e = self.transform.basis.get_euler
let n = e.nm
let v = vec3(nm(n.x).rad_to_deg, nm(n.y).rad_to_deg, nm(n.z).rad_to_deg)
let m = if v.z > 0: 1.0 else: -1.0
let v2 = vec3(0.0, (v.x - v.y) * m, 0.0)
a.set_result(v2.to_node)
return false
self.on_script_loaded()
if not state.paused:
self.update_running_state ctx.engine.run()
except VMQuit as e:
if retry_failures:
self.script_ctx.engine.running = false
failed.add (self, e)
else:
self.error(e)
proc create_new(self: Unit): bool =
let unit = active_unit()
let ae = active_engine()
let clone = self.clone(unit, active_ctx())
clone.script_ctx = ScriptCtx.init
clone.script_ctx.is_clone = true
clone.script_ctx.script = self.script_file
let new_engine = clone.script_ctx.engine
unit_ctxs[new_engine] = clone
ae.callback = proc(delta: float): bool =
if not new_engine.initialized:
clone.code.value = clone.script_file.read_file
true
else:
false
set_active(ae)
unit.units.add(clone)
result = true

131
src/engine/contexts_del.nim Normal file
View File

@ -0,0 +1,131 @@
import std / [monotimes, os, hashes, sets, strutils, sugar, math]
import pkg / godot except print
import pkg / print
import godotapi / [node]
import core, engine/engine, models, globals
export engine
let state = GameState.active
let config = state.config
#retry_failures* = false
#proc load_script*(self: Unit, script = "")
#proc create_new(self: Unit): bool
proc destroy*(ctx: ScriptCtx) =
if ctx.engine in ctxs:
ctxs.del(ctx.engine)
# proc is_script_loadable*(self: Unit): bool =
# let ctx = self.script_ctx
# ctx.script != "none" and ctx.script.file_exists
proc error*(self: Unit, ex: ref VMQuit) =
var ctx = self.script_ctx
ctx.engine.running = false
when defined(enu_simulate):
raise ex
else:
let (root, _) = self.find_root(true)
ctx = root.script_ctx
if ctx.retry_on_nil and ("attempt to access a nil address" in ex.msg):
root.code.touch root.code.value
else:
state.err "Exception executing " & self.script_ctx.script
state.err ex.msg
state.console.show_errors.value = true
proc retry_failed_scripts* =
var prev_failed = failed
failed = @[]
for f in prev_failed:
echo "retrying: ", f.unit.script_ctx.script
f.unit.load_script()
if failed.len > 0 and prev_failed.len == failed.len:
prev_failed = failed
for f in prev_failed:
f.unit.error(f.ex)
proc update_running_state(self: Unit, running: bool) =
self.script_ctx.engine.running = running
if not running:
self.load_vars()
state.debug(self.script_ctx.script & " done.")
proc remove_module*(file_name: string) =
module_names.excl file_name.split_file.name
proc advance*(self: Unit, delta: float64) =
let now = get_mono_time()
let c = self.script_ctx
let e = c.engine
if self of Build:
let self = Build(self)
self.voxels_remaining_this_frame += self.voxels_per_frame
var resume_script = true
try:
while resume_script and not state.paused:
resume_script = false
if e.callback == nil or (not e.callback(delta)):
c.timer = MonoTime.high
discard e.call_proc("set_action_running", e.module_name, false)
self.update_running_state e.resume()
if self of Build:
let self = Build(self)
if self.voxels_per_frame > 0 and e.running and self.voxels_remaining_this_frame >= 1:
resume_script = true
elif now >= c.timer:
c.timer = now + ADVANCE_STEP
e.saved_callback = e.callback
e.callback = nil
discard e.resume()
except VMQuit as e:
self.error(e)
proc begin_move(self: Unit, direction: Vector3, steps: float, move_mode: int): bool =
self.load_vars()
var steps = steps
var direction = direction
if steps < 0:
steps = steps * -1
direction = direction * -1
active_engine().callback = self.on_begin_move(direction, steps, move_mode)
result = not active_engine().callback.is_nil
proc begin_turn(self: Unit, direction: Vector3, degrees: float, move_mode: int): bool =
self.load_vars()
var degrees = degrees
var direction = direction
if degrees < 0:
degrees = degrees * -1
direction = direction * -1
active_engine().callback = self.on_begin_turn(direction, degrees, move_mode)
result = not active_engine().callback.is_nil
proc create_new(self: Unit): bool =
let unit = active_unit()
let ae = active_engine()
let clone = self.clone(unit, active_ctx())
clone.script_ctx = ScriptCtx.init
clone.script_ctx.is_clone = true
clone.script_ctx.script = self.script_file
let new_engine = clone.script_ctx.engine
unit_ctxs[new_engine] = clone
ae.callback = proc(delta: float): bool =
if not new_engine.initialized:
clone.code.value = clone.script_file.read_file
true
else:
false
set_active(ae)
unit.units.add(clone)
result = true

View File

@ -1,209 +0,0 @@
import std / [os, strformat, with]
import pkg / godot, pkg / compiler / [vm, vmdef, options, lineinfos, ast]
import core, types, eval
export Interpreter
export VmArgs, get_float, get_int, get_string, get_bool, set_result, to_int
type
VMError* = object of CatchableError
VMQuit* = object of VMError
info*: TLineInfo
VMPause* = object of CatchableError
Engine* = ref object
ctx: PCtx
pc: int
tos: PStackFrame
line_changed*: proc(current, previous: TLineInfo)
current_line*: TLineInfo
previous_line: TLineInfo
pause_requested: bool
is_main_module: bool
initialized*: bool
code: string
module_name*: string
file_name: string
exit_code*: Option[int]
errors*: seq[tuple[msg: string, info: TLineInfo]]
callback*: Callback
saved_callback*: Callback
running*: bool
const
STDLIB_PATHS = [".", "core", "pure", "pure/collections", "pure/concurrency", "std", "fusion"]
MAIN_SCRIPT = "scripts/main.nim"
var
interpreter: Interpreter
current: Engine
proc active_engine*(): Engine = current
proc set_active*(e: Engine) =
current = e
proc init*(t: typedesc[Engine]): Engine =
result = Engine()
proc init_interpreter(script_dir, vmlib: string) =
let std_paths = STDLIB_PATHS.map_it join_path(vmlib, "stdlib", it)
let source_paths = std_paths & join_path(vmlib, "enu") & @[parent_dir MAIN_SCRIPT] & @[script_dir]
interpreter = create_interpreter(MAIN_SCRIPT, source_paths)
log_trace("create_interpreter")
with interpreter:
register_error_hook proc(config, info, msg, severity: auto) {.gcsafe.} =
let e = active_engine()
if severity == Severity.Error and config.error_counter >= config.error_max:
let file_name = if info.file_index.int >= 0:
config.m.file_infos[info.file_index.int].full_path.string
else:
"???"
let msg = &"{file_name}({int info.line},{int info.col}): {msg}"
echo "error: ", msg, " from ", e.file_name
e.errors.add (msg, info)
e.exit_code = some(99)
raise (ref VMQuit)(info: info, msg: msg)
register_enter_hook proc(c, pc, tos, instr: auto) =
let e = active_engine()
let info = c.debug[pc]
if e.previous_line != info:
let config = interpreter.config
if info.file_index.int >= 0 and info.file_index.int < config.m.file_infos.len:
let file_name = config.m.file_infos[info.file_index.int].full_path.string
if file_name == e.file_name:
if e.line_changed != nil:
e.line_changed(info, e.previous_line)
(e.previous_line, e.current_line) = (e.current_line, info)
if e.pause_requested:
e.pause_requested = false
e.ctx = c
e.pc = pc
e.tos = tos
raise new_exception(VMPause, "vm paused")
proc pause*(e: Engine) =
e.pause_requested = true
proc load*(e: Engine, script_dir, file_name, code, vmlib: string, module_suffix = "") =
e.ctx = nil
e.pc = 0
e.tos = nil
e.code = code
if module_suffix == "":
e.module_name = file_name.split_file.name
e.file_name = file_name
else:
e.module_name = file_name.split_file.name & module_suffix
e.file_name = e.module_name
set_active e
if interpreter.is_nil:
init_interpreter(script_dir, vmlib)
e.is_main_module = true
e.initialized = true
proc run*(e: Engine): bool =
set_active e
e.exit_code = none(int)
e.errors = @[]
try:
interpreter.load_module(e.file_name, e.code)
false
except VMPause:
e.exit_code.is_none
proc get_vec3*(a: VmArgs, pos: int): Vector3 =
proc float_val(node: PNode, name: string): float =
assert node.kind == nkExprColonExpr
assert node.sons[0].sym.kind == skField
assert node.sons[0].sym.name.s == name
result = node.sons[1].float_val
let
fields = a.get_node(0).sons
x = float_val(fields[1], "x")
y = float_val(fields[2], "y")
z = float_val(fields[3], "z")
result = vec3(x, y, z)
# adapted from https://github.com/h0lley/embeddedNimScript/blob/6101fb37d4bd3f947db86bac96f53b35d507736a/embeddedNims/enims.nim#L31
proc to_node*(val: int): PNode = new_int_node(nkIntLit, val)
proc to_node*(val: float): PNode = new_float_node(nkFloatLit, val)
proc to_node*(val: string): PNode = new_str_node(nkStrLit, val)
proc to_node*(val: bool): PNode = val.ord.to_node
proc to_node*(val: enum): PNode = val.ord.to_node
proc to_node*(list: open_array[int|float|string|bool|enum]): PNode =
result = nkBracket.new_node
result.sons.initialize(list.len)
for i in 0..list.high: result.sons[i] = list[i].to_node()
proc to_node*(tree: tuple|object): PNode =
result = nkPar.new_tree
for field in tree.fields:
result.sons.add(field.to_node)
proc to_node*(tree: ref tuple|ref object): PNode =
result = nkPar.new_tree
if tree.is_nil: return result
for field in tree.fields:
result.sons.add(field.to_node)
proc call_proc*(e: Engine, proc_name: string, module_name = "", args: varargs[PNode, to_node]): PNode {.discardable.}=
let foreign_proc = interpreter.select_routine(proc_name, module_name = module_name)
if foreign_proc == nil:
raise new_exception(VMError, &"script does not export a proc of the name: '{proc_name}'")
return interpreter.call_routine(foreign_proc, args)
proc call*(e: Engine, proc_name: string): bool =
set_active e
try:
discard e.call_proc(proc_name)
false
except VMPause:
e.exit_code.is_none
proc expose*(e: Engine, proc_name: string,
routine: proc(a: VmArgs): bool) {.gcsafe.} =
interpreter.implement_routine "*", e.module_name, proc_name, proc(a: VmArgs) {.gcsafe.} =
if routine(a):
active_engine().pause()
proc call_float*(e: Engine, proc_name: string): float =
e.call_proc(proc_name).get_float()
proc get_var*(e: Engine, var_name: string, module_name: string): PNode =
let sym = interpreter.select_unique_symbol(var_name, module_name = module_name)
interpreter.get_global_value(sym)
proc get_float*(e: Engine, var_name: string, module_name = ""): float =
e.get_var(var_name, module_name).get_float
proc get_int*(e: Engine, var_name: string, module_name = ""): int =
e.get_var(var_name, module_name).get_int.to_int
proc get_bool*(e: Engine, var_name: string, module_name = ""): bool =
let b = e.get_var(var_name, module_name).get_int
return b == 1
proc get_string*(e: Engine, var_name: string, module_name = ""): string =
result = e.get_var(var_name, module_name).get_str
proc call_int*(e: Engine, proc_name: string): int =
e.call_proc(proc_name).get_int.to_int
proc reset_interpreter*() =
interpreter = nil
current = nil
proc resume*(e: Engine): bool =
assert not e.ctx.is_nil
assert e.pc > 0
assert not e.tos.is_nil
set_active e
result = try:
discard exec_from_ctx(e.ctx, e.pc, e.tos)
false
except VMPause:
e.exit_code.is_none

View File

@ -1,2 +0,0 @@
type
Callback* = proc(delta: float): bool

View File

@ -4,7 +4,7 @@ import godotapi / [input, input_event, gd_os, node, scene_tree, viewport,
packed_scene, sprite, control, viewport,
performance, label, theme, dynamic_font, resource_loader, main_loop,
gd_os, project_settings, input_map, input_event, input_event_action]
import core, globals, controllers/unit_controllers, models / serializers
import core, globals, controllers / [node_controllers, script_controllers], models / serializers
type
UserConfig = object
@ -30,7 +30,8 @@ gdobj Game of Node:
scale_factor* = 0.0
rescale_at = get_mono_time()
save_at = get_mono_time() + auto_save_interval
unit_controller: UnitController
node_controller: NodeController
script_controller: ScriptController
method process*(delta: float) =
let time = get_mono_time()
@ -84,7 +85,6 @@ gdobj Game of Node:
proc init* =
state.nodes.game = self
self.unit_controller = UnitController.init
let
screen_scale = get_screen_scale(-1)
work_dir = get_user_data_dir()
@ -127,6 +127,8 @@ gdobj Game of Node:
config.lib_dir = join_path(exe_dir.parent_dir, "lib", "vmlib")
print config
self.node_controller = NodeController.init
self.script_controller = ScriptController.init
method ready* =
state.nodes.data = state.nodes.game.find_node("Level").get_node("data")
@ -228,7 +230,7 @@ gdobj Game of Node:
elif event.is_action_pressed("save_and_reload"):
echo "reload all"
save_world()
UnitController.reload_all()
self.script_controller.reload_all()
self.get_tree().set_input_as_handled()
elif event.is_action_pressed("pause"):
state.paused = not state.paused

View File

@ -1,4 +1,5 @@
include compiler/nimeval
export Interpreter, VmArgs, PCtx, PStackFrame, TLineInfo
# from nimeval. Added moduleName
proc selectUniqueSymbol*(i: Interpreter; name: string;
@ -51,12 +52,11 @@ proc load_module*(i: Interpreter, module_name, code: string) =
proc config*(i: Interpreter): ConfigRef = i.graph.config
when callVMExecHooks:
proc registerExitHook*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame)) =
(PCtx i.graph.vm).exitHook = hook
proc registerExitHook*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame)) =
(PCtx i.graph.vm).exitHook = hook
proc registerEnterHook*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame, instr: TInstr)) =
(PCtx i.graph.vm).enterHook = hook
proc registerEnterHook*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame, instr: TInstr)) =
(PCtx i.graph.vm).enterHook = hook
proc registerLeaveHook*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame, instr: TInstr)) =
(PCtx i.graph.vm).leaveHook = hook
proc registerLeaveHook*(i: Interpreter, hook: proc (c: PCtx, pc: int, tos: PStackFrame, instr: TInstr)) =
(PCtx i.graph.vm).leaveHook = hook

64
src/libs/interpreters.nim Normal file
View File

@ -0,0 +1,64 @@
import std / [os, strformat, with, importutils]
import pkg / compiler / ast except new_node
import pkg / godot except print
import pkg / [print], pkg / compiler / [vm, vmdef, options, lineinfos]
import core, eval
import models/types
export Interpreter, VmArgs, set_result
const
STDLIB_PATHS = [".", "core", "pure", "pure/collections", "pure/concurrency", "std", "fusion"]
MAIN_SCRIPT = "scripts/main.nim"
private_access ScriptCtx
proc init*(_: type Interpreter, script_dir, vmlib: string): Interpreter =
let std_paths = STDLIB_PATHS.map_it join_path(vmlib, "stdlib", it)
let source_paths = std_paths & join_path(vmlib, "enu") & @[parent_dir MAIN_SCRIPT] & @[script_dir]
print source_paths
result = create_interpreter(MAIN_SCRIPT, source_paths)
proc pause*(ctx: ScriptCtx) =
ctx.pause_requested = true
proc load*(self: ScriptCtx, script_dir, file_name, code, vmlib: string) =
self.ctx = nil
self.pc = 0
self.tos = nil
self.code = code
self.module_name = file_name.split_file.name
self.file_name = file_name
proc run*(self: ScriptCtx): bool =
self.exit_code = none(int)
self.errors = @[]
try:
self.interpreter.load_module(self.file_name, self.code)
result = false
except VMPause:
result = self.exit_code.is_none
proc call_proc*(self: ScriptCtx, proc_name: string, args: varargs[PNode]): tuple[paused: bool, result: PNode] =
let foreign_proc = self.interpreter.select_routine(proc_name, module_name = self.module_name)
if foreign_proc == nil:
raise new_exception(VMError, &"script does not export a proc of the name: '{proc_name}'")
result = try:
(false, self.interpreter.call_routine(foreign_proc, args))
except VMPause:
(self.exit_code.is_none, nil)
proc get_var*(self: ScriptCtx, var_name: string, module_name: string): PNode =
let sym = self.interpreter.select_unique_symbol(var_name, module_name = module_name)
self.interpreter.get_global_value(sym)
proc resume*(self: ScriptCtx): bool =
assert not self.ctx.is_nil
assert self.pc > 0
assert not self.tos.is_nil
result = try:
discard exec_from_ctx(self.ctx, self.pc, self.tos)
false
except VMPause:
self.exit_code.is_none

View File

@ -1,5 +1,5 @@
import pkg / [model_citizen]
import models / [types, states, units, builds, bots, ground, colors, scripts, player_model]
import models / [types, states, units, builds, bots, ground, colors, player_model]
export model_citizen except `%`
export types, states, units, builds, bots, ground, colors, scripts
export types, states, units, builds, bots, ground, colors

View File

@ -1,12 +1,12 @@
import std / [math, sugar, monotimes]
import pkg / model_citizen
import core, models / [types, states, scripts, units], engine / engine
import core, models / [types, states, units]
include "default_robot.nim.nimf"
let state = GameState.active
method code_template*(self: Bot, imports: string): string =
result = default_robot(self.script_file, imports, self.script_ctx.is_clone)
result = default_robot(self.script_file, imports)
method on_begin_move*(self: Bot, direction: Vector3, steps: float, moving_mode: int): Callback =
# move_mode param is ignored
@ -24,7 +24,8 @@ method on_begin_move*(self: Bot, direction: Vector3, steps: float, moving_mode:
else:
self.velocity.touch(moving * self.speed)
return true
active_ctx().start_advance_timer()
# TODO?
# active_ctx().start_advance_timer()
method on_begin_turn*(self: Bot, axis: Vector3, degrees: float, move_mode: int): Callback =
# move mode param is ignored
@ -39,24 +40,25 @@ method on_begin_turn*(self: Bot, axis: Vector3, degrees: float, move_mode: int):
else:
self.transform.basis = final_basis
false
active_ctx().start_advance_timer()
# TODO?
# active_ctx().start_advance_timer()
method load_vars*(self: Bot) =
let old_speed = self.speed
let ctx = self.script_ctx
self.speed = ctx.engine.get_float("speed", ctx.engine.module_name)
let scale = ctx.engine.get_float("scale", ctx.engine.module_name)
if scale != self.scale:
var basis = self.transform.basis
basis.set_scale(vec3(scale, scale, scale))
self.transform.basis = basis
self.scale = scale
# method load_vars*(self: Bot) =
# let old_speed = self.speed
# let ctx = self.script_ctx
# self.speed = ctx.engine.get_float("speed", ctx.engine.module_name)
# let scale = ctx.engine.get_float("scale", ctx.engine.module_name)
# if scale != self.scale:
# var basis = self.transform.basis
# basis.set_scale(vec3(scale, scale, scale))
# self.transform.basis = basis
# self.scale = scale
method on_script_loaded*(self: Bot) =
let e = self.script_ctx.engine
e.expose "play", proc(a: VmArgs): bool =
self.animation.value = get_string(a, 0)
return false
# method on_script_loaded*(self: Bot) =
# let e = self.script_ctx.engine
# e.expose "play", proc(a: VmArgs): bool =
# self.animation.value = get_string(a, 0)
# return false
proc bot_at*(state: GameState, position: Vector3): Bot =
for unit in state.units:
@ -64,6 +66,7 @@ proc bot_at*(state: GameState, position: Vector3): Bot =
return Bot(unit)
method reset*(self: Bot) =
self.transform.value = self.start_transform
self.animation.value = ""
self.units.clear()
@ -79,7 +82,8 @@ proc init*(_: type Bot, transform = Transform.init, clone_of: Bot = nil, global
animation: ZenValue[string].init,
energy: ZenValue[float].init,
speed: 1.0,
clone_of: clone_of
clone_of: clone_of,
frame_delta: ZenValue[float].init
)
if global: self.flags += Global
@ -108,7 +112,7 @@ proc init*(_: type Bot, transform = Transform.init, clone_of: Bot = nil, global
result = self
method clone*(self: Bot, clone_to: Unit, ctx: ScriptCtx): Unit =
method clone*(self: Bot, clone_to: Unit): Unit =
var transform = clone_to.transform.value
result = Bot.init(transform = transform, clone_of = self)
result.parent = clone_to

View File

@ -1,6 +1,6 @@
import std / [hashes, tables, sets, options, sequtils, math, wrapnils, monotimes, sugar]
import pkg / [model_citizen, print]
import core, models / [types, states, bots, colors, units, scripts], engine / engine
import core, models / [types, states, bots, colors, units]
const BufferSize = vec3(16, 16, 16)
include "default_builder.nim.nimf"
@ -213,7 +213,7 @@ method on_begin_move*(self: Build, direction: Vector3, steps: float, move_mode:
self.voxels_remaining_this_frame -= 1
self.drop_block()
result = count.float < steps
active_ctx().start_advance_timer()
#active_ctx().start_advance_timer()
method on_begin_turn*(self: Build, axis: Vector3, degrees: float, move_mode: int): Callback =
let map = {LEFT: UP, RIGHT: DOWN, UP: RIGHT, DOWN: LEFT}.to_table
@ -235,7 +235,7 @@ method on_begin_turn*(self: Build, axis: Vector3, degrees: float, move_mode: int
# TODO?
self.transform.value = final_transform
false
active_ctx().start_advance_timer()
#active_ctx().start_advance_timer()
else:
let axis = self.draw_transform.basis.xform(axis)
self.draw_transform.basis = self.draw_transform.basis.rotated(axis, deg_to_rad(degrees))
@ -245,6 +245,7 @@ proc reset_state(self: Build) =
self.draw_transform = Transform.init
method reset*(self: Build) =
self.transform.value = self.start_transform
self.reset_state()
let chunks = self.chunks.value
for chunk_id, chunk in chunks:
@ -256,65 +257,65 @@ method reset*(self: Build) =
self.chunks.del(chunk_id)
self.units.clear()
proc set_vars*(self: Build) =
let engine = self.script_ctx.engine
let module_name = engine.module_name
# proc set_vars*(self: Build) =
# let engine = self.script_ctx.engine
# let module_name = engine.module_name
#
# engine.call_proc("set_vars", module_name = module_name, action_index(self.color).int,
# self.drawing, self.speed, self.scale, self.energy.value)
#
# method load_vars*(self: Build) =
# let old_speed = self.speed
# let ctx = self.script_ctx
# self.speed = ctx.engine.get_float("speed", ctx.engine.module_name)
#
# let
# e = ctx.engine
# scale_factor = ctx.engine.get_float("scale", e.module_name).round(3)
# self.color = action_colors[Colors(ctx.engine.get_int("color", e.module_name))]
# self.drawing = ctx.engine.get_bool("drawing", e.module_name)
# self.voxels_per_frame = if self.speed == 0:
# float.high
# else:
# self.speed
# if self.speed != old_speed:
# self.voxels_remaining_this_frame = 0
# if scale_factor != self.scale.round(3):
# self.scale = scale_factor
# var basis = self.transform.basis
# basis.set_scale(vec3(scale_factor, scale_factor, scale_factor))
# self.transform.basis = basis
# self.energy.value = ctx.engine.get_float("energy", e.module_name).round(3)
#
# self.set_vars()
engine.call_proc("set_vars", module_name = module_name, action_index(self.color).int,
self.drawing, self.speed, self.scale, self.energy.value)
method load_vars*(self: Build) =
let old_speed = self.speed
let ctx = self.script_ctx
self.speed = ctx.engine.get_float("speed", ctx.engine.module_name)
let
e = ctx.engine
scale_factor = ctx.engine.get_float("scale", e.module_name).round(3)
self.color = action_colors[Colors(ctx.engine.get_int("color", e.module_name))]
self.drawing = ctx.engine.get_bool("drawing", e.module_name)
self.voxels_per_frame = if self.speed == 0:
float.high
else:
self.speed
if self.speed != old_speed:
self.voxels_remaining_this_frame = 0
if scale_factor != self.scale.round(3):
self.scale = scale_factor
var basis = self.transform.basis
basis.set_scale(vec3(scale_factor, scale_factor, scale_factor))
self.transform.basis = basis
self.energy.value = ctx.engine.get_float("energy", e.module_name).round(3)
self.set_vars()
method on_script_loaded*(self: Build) =
var save_points: Table[string, tuple[transform: Transform, color: Color, drawing: bool]]
let e = self.script_ctx.engine
self.voxels_remaining_this_frame = 0
e.expose "set_energy", proc(a: VmArgs): bool =
self.energy.value = get_float(a, 0)
false
e.expose "save", proc(a: VmArgs): bool =
self.load_vars()
let name = get_string(a, 0)
save_points[name] = (self.transform.value, self.color, self.drawing)
false
e.expose "restore", proc(a: VmArgs): bool =
let name = get_string(a, 0)
(self.transform.value, self.color, self.drawing) = save_points[name]
self.set_vars()
false
e.expose "reset", proc(a: VmArgs): bool =
let clear = get_bool(a, 0)
if clear:
self.reset()
else:
self.reset_state()
false
e.expose "load_defaults", proc(a: VmArgs): bool =
self.set_vars()
false
# method on_script_loaded*(self: Build) =
# var save_points: Table[string, tuple[transform: Transform, color: Color, drawing: bool]]
# let e = self.script_ctx.engine
# self.voxels_remaining_this_frame = 0
# e.expose "set_energy", proc(a: VmArgs): bool =
# self.energy.value = get_float(a, 0)
# false
# e.expose "save", proc(a: VmArgs): bool =
# self.load_vars()
# let name = get_string(a, 0)
# save_points[name] = (self.transform.value, self.color, self.drawing)
# false
# e.expose "restore", proc(a: VmArgs): bool =
# let name = get_string(a, 0)
# (self.transform.value, self.color, self.drawing) = save_points[name]
# self.set_vars()
# false
# e.expose "reset", proc(a: VmArgs): bool =
# let clear = get_bool(a, 0)
# if clear:
# self.reset()
# else:
# self.reset_state()
# false
# e.expose "load_defaults", proc(a: VmArgs): bool =
# self.set_vars()
# false
proc init*(_: type Build, transform = Transform.init, color = default_color,
clone_of: Unit = nil, global = true, bot_collisions = true): Build =
@ -335,7 +336,8 @@ proc init*(_: type Build, transform = Transform.init, color = default_color,
bounds: Zen.init(init_aabb(vec3(), vec3(-1, -1, -1))),
speed: 1.0,
clone_of: clone_of,
bot_collisions: bot_collisions
bot_collisions: bot_collisions,
frame_delta: ZenValue[float].init
)
if global: self.flags += Global
@ -385,7 +387,7 @@ method off_collision*(self: Build, partner: Model) =
if self.script_ctx:
self.script_ctx.timer = get_mono_time()
method clone*(self: Build, clone_to: Unit, ctx: ScriptCtx): Unit =
method clone*(self: Build, clone_to: Unit): Unit =
var transform = clone_to.transform.value
var global = true
if clone_to of Build:

View File

@ -1,10 +1,12 @@
#? stdtmpl
#proc default_robot(file_name, imports: string, is_clone: bool): string =
import types, class_macros, players
const is_clone = ${is_clone}
#proc default_robot(file_name, imports: string): string =
import system except echo
import types, class_macros, players, state_machine
let global_default = true
${imports}
load_enu_script "${file_name}", "robot", ${is_clone}
{.experimental: "overloadableEnums".}
load_enu_script "${file_name}", "robot"
quit()
me.quit()

View File

@ -1,14 +0,0 @@
import std / [monotimes]
import models / [types], engine / engine
import core
const ADVANCE_STEP* = 0.5.seconds
var ctxs*: Table[Engine, ScriptCtx]
var unit_ctxs*: Table[Engine, Unit]
proc active_unit*(): Unit = unit_ctxs[active_engine()]
proc active_ctx*(): ScriptCtx = ctxs[active_engine()]
proc start_advance_timer*(ctx: ScriptCtx) =
ctx.timer = get_mono_time() + ADVANCE_STEP

View File

@ -1,6 +1,6 @@
import std / [json, jsonutils, sugar, tables, os, strutils]
import pkg / print
import core, models, engine / contexts
import core, models
let state = GameState.active
@ -130,8 +130,8 @@ proc load_units(parent: Unit) =
proc load_world*() =
dont_join = true
retry_failures = true
#retry_failures = true
load_units(nil)
retry_failed_scripts()
retry_failures = false
#retry_failed_scripts()
#retry_failures = false
dont_join = false

View File

@ -3,11 +3,11 @@ import godotapi/node
import pkg/model_citizen
import pkg/core/godotcoretypes except Color
import pkg / core / [vector3, basis, aabb, godotbase]
import core, models/colors, engine/engine, utils/transforms
import core, models/colors, utils/transforms, libs/eval
export Vector3, Transform, vector3, transforms, basis, AABB, aabb
export godotbase except print
export Interpreter
type
TargetFlags* = enum
@ -51,7 +51,6 @@ type
paused*: bool
Model* = ref object of RootObj
target_point*: Vector3
target_normal*: Vector3
flags*: ZenSet[ModelFlags]
@ -82,6 +81,7 @@ type
energy*: ZenValue[float]
clone_of*: Unit
collisions*: seq[tuple[model: Model, normal: Vector3]]
frame_delta*: ZenValue[float]
Bot* = ref object of Unit
animation*: ZenValue[string]
@ -107,16 +107,6 @@ type
bounds*: ZenValue[AABB]
bot_collisions*: bool
Callback* = proc(delta: float): bool
ScriptCtx* = ref object
script*: string
engine*: Engine
timer*: MonoTime
load_vars*: proc()
is_clone*: bool
speed*: float
retry_on_nil*: bool
Config* = ref object
font_size*: int
dock_icon_size*: float
@ -129,6 +119,39 @@ type
scene*: string
lib_dir*: string
ScriptCtx* = ref object
script*: string
timer*: MonoTime
load_vars*: proc()
is_clone*: bool
ctx: PCtx
pc: int
tos: PStackFrame
line_changed*: proc(current, previous: TLineInfo)
current_line*: TLineInfo
previous_line: TLineInfo
pause_requested: bool
module_name*: string
file_name: string
exit_code*: Option[int]
errors*: seq[tuple[msg: string, info: TLineInfo]]
callback*: Callback
saved_callback*: Callback
action_running*: bool
running*: bool
interpreter*: Interpreter
code*: string
VMError* = object of CatchableError
VMQuit* = object of VMError
info*: TLineInfo
VMPause* = object of CatchableError
Callback* = proc(delta: float): bool
# TODO: this shouldn't be here
proc local_to*(self: Vector3, unit: Unit): Vector3 =
result = self
var unit = unit

View File

@ -1,7 +1,7 @@
import std / [os, sugar]
import pkg / model_citizen
import godotapi / node
import core, models / [types, states], engine / engine
import core, models / [types, states], libs / interpreters
proc init*(_: type Model, node: Node): Model =
result = Model(flags: ZenSet[ModelFlags].init, node: node)
@ -50,7 +50,7 @@ method on_begin_move*(self: Unit, direction: Vector3, steps: float, move_mode: i
method on_begin_turn*(self: Unit, direction: Vector3, degrees: float, move_mode: int): Callback {.base.} =
quit "override me"
method clone*(self: Unit, clone_to: Unit, ctx: ScriptCtx): Unit {.base.} =
method clone*(self: Unit, clone_to: Unit): Unit {.base.} =
quit "override me"
method code_template*(self: Unit, imports: string): string {.base.} =

View File

@ -5,7 +5,7 @@ import godotapi / [kinematic_body, spatial, input, input_event,
input_event_mouse_motion, input_event_joypad_motion,
ray_cast, scene_tree, input_event_pan_gesture, viewport, camera, global_constants,
collision_shape, kinematic_collision]
import core, globals, game, engine/engine, engine/contexts, models / [units, player_model], world / nodes
import core, globals, game, models / [units, player_model], world / nodes
import aim_target, models
proc handle_collisions(self: PlayerModel, collisions: seq[KinematicCollision]) {.inline.} =
@ -107,7 +107,8 @@ gdobj Player of KinematicBody:
self.position_start = self.camera_rig.translation
state.nodes.player = self
self.load_script()
# TODO
# self.load_script()
state.target_flags.changes:
if MouseCaptured.removed:
self.skip_next_mouse_move = true
@ -148,28 +149,28 @@ gdobj Player of KinematicBody:
self.aim_ray.cast_to = vec3(0, 0, -100)
self.aim_target.update(self.aim_ray)
proc load_script() =
let ctx = ScriptCtx.init
ctx.script = config.lib_dir & "/enu/players.nim"
let code = read_file ctx.script
ctx.engine.load(config.script_dir, ctx.script, code, config.lib_dir, "")
ctx.engine.expose "quit", proc(a: VmArgs): bool =
engine.pause(ctx.engine)
ctx.engine.running = false
result = false
ctx.engine.expose "get_position", proc(a: VmArgs): bool =
let n = self.global_transform.origin.to_node
a.set_result(n)
return false
ctx.engine.expose "get_rotation", proc(a: VmArgs): bool =
a.set_result(self.rotation_degrees.to_node)
return false
ctx.engine.expose "set_velocity", proc(a: VmArgs): bool =
let v = a.get_vec3(0)
self.velocity = v
ctx.engine.expose "get_velocity", proc(a: VmArgs): bool =
a.set_result(self.velocity.to_node)
discard ctx.engine.run()
# proc load_script() =
# let ctx = ScriptCtx.init
# ctx.script = config.lib_dir & "/enu/players.nim"
# let code = read_file ctx.script
# ctx.engine.load(config.script_dir, ctx.script, code, config.lib_dir, "")
# ctx.engine.expose "quit", proc(a: VmArgs): bool =
# engine.pause(ctx.engine)
# ctx.engine.running = false
# result = false
# ctx.engine.expose "get_position", proc(a: VmArgs): bool =
# let n = self.global_transform.origin.to_node
# a.set_result(n)
# return false
# ctx.engine.expose "get_rotation", proc(a: VmArgs): bool =
# a.set_result(self.rotation_degrees.to_node)
# return false
# ctx.engine.expose "set_velocity", proc(a: VmArgs): bool =
# let v = a.get_vec3(0)
# self.velocity = v
# ctx.engine.expose "get_velocity", proc(a: VmArgs): bool =
# a.set_result(self.velocity.to_node)
# discard ctx.engine.run()
method physics_process*(delta: float) =
trace:

View File

@ -3,7 +3,7 @@ import pkg / [godot, model_citizen]
import pkg / compiler / [lineinfos]
import godotapi / [text_edit, scene_tree, node, input_event, global_constants,
input_event_key, style_box_flat]
import core, globals, engine / engine
import core, globals
import models except Color
let state = GameState.active
@ -14,22 +14,22 @@ gdobj Editor of TextEdit:
mouse_was_captured = false
og_bg_color: Color
dirty = false
open_engine: Engine
open_script_ctx: ScriptCtx
proc set_open_engine() =
proc set_open_script_ctx() =
# TODO: yuck
var current = self.open_engine
var current = self.open_script_ctx
if not state.open_unit.value.is_nil and state.open_unit.value.script_ctx:
current = state.open_unit.value.script_ctx.engine
if self.open_engine != current:
if self.open_engine:
self.open_engine.line_changed = nil
current = state.open_unit.value.script_ctx
if self.open_script_ctx != current:
if self.open_script_ctx:
self.open_script_ctx.line_changed = nil
if not state.open_unit.value.is_nil and not state.open_unit.value.script_ctx.is_nil:
self.open_engine = state.open_unit.value.script_ctx.engine
if self.open_engine:
self.executing_line = int self.open_engine.current_line.line - 1
self.open_engine.line_changed = proc(current: TLineInfo, previous: TLineInfo) =
self.open_script_ctx = state.open_unit.value.script_ctx
if self.open_script_ctx:
self.executing_line = int self.open_script_ctx.current_line.line - 1
self.open_script_ctx.line_changed = proc(current: TLineInfo, previous: TLineInfo) =
self.executing_line = int current.line - 1
proc indent_new_line() =
@ -82,9 +82,9 @@ gdobj Editor of TextEdit:
proc highlight_errors =
self.clear_executing_line()
self.set_open_engine()
if not self.open_engine.is_nil:
for err in self.open_engine.errors:
self.set_open_script_ctx()
if not self.open_script_ctx.is_nil:
for err in self.open_script_ctx.errors:
self.set_line_as_marked(int64(err.info.line - 1), true)
proc `executing_line=`*(line: int) =
@ -124,7 +124,7 @@ gdobj Editor of TextEdit:
elif Editing.added:
self.visible = true
self.set_open_engine()
self.set_open_script_ctx()
self.text = state.open_unit.value.code.value
self.grab_focus()
self.clear_errors()
@ -133,8 +133,8 @@ gdobj Editor of TextEdit:
elif Editing.removed:
self.release_focus()
self.visible = false
if self.open_engine:
self.open_engine.line_changed = nil
self.open_engine = nil
if self.open_script_ctx:
self.open_script_ctx.line_changed = nil
self.open_script_ctx = nil
self.configure_highlighting()

View File

@ -4,7 +4,7 @@ import pkg/model_citizen
import godotapi / [scene_tree, kinematic_body, material, mesh_instance, spatial,
input_event, animation_player, resource_loader, packed_scene]
import globals, core, print
import engine / [contexts, engine], models / [types, bots, units]
import models / [types, bots, units]
gdobj BotNode of KinematicBody:
var
@ -77,9 +77,7 @@ gdobj BotNode of KinematicBody:
self.unit.transform.pause self.transform_zid:
self.unit.transform.value = self.transform
if self.unit.script_ctx:
if self.unit.script_ctx.engine.running:
self.unit.advance(delta)
self.unit.frame_delta.touch delta
let bot_scene = load("res://components/BotNode.tscn") as PackedScene
proc init*(_: type BotNode): BotNode =

View File

@ -4,7 +4,6 @@ import pkg / [print, model_citizen]
import godotapi / [node, voxel_terrain, voxel_mesher_blocky, voxel_tool, voxel_library, shader_material,
resource_loader, packed_scene]
import models / [types, builds, colors, units], globals
import engine / contexts
const
highlight_energy = 1.0
@ -100,13 +99,11 @@ gdobj BuildNode of VoxelTerrain:
method process(delta: float) =
if self.unit:
if self.unit.script_ctx:
if self.unit.script_ctx.engine.running:
self.unit.advance(delta)
# self.unit.transform.pause self.transform_zid:
# self.unit.transform.value = self.transform
self.unit.frame_delta.touch delta
proc setup*(unit: Build) =
let was_skipping_join = dont_join
dont_join = true

2
tests/TEST_README.md Normal file
View File

@ -0,0 +1,2 @@
This isn't a test suite. These are tests that I found useful for whatever reason during development. They don't
validate much of anything, and many will fail. Eventually this will be fixed.

View File

@ -4,3 +4,4 @@ switch("path", "$projectDir/../../vmlib")
switch("path", this_dir())
--define:vmExecHooks
--define:useRealtimeGC
--experimental:dynamicBindSym

View File

@ -1,10 +1,10 @@
import enu/types, enu/class_macros
import macros except name
import pkg / print
const is_clone = false
import enu/base_api
import enu/state_machine
load_enu_script "potato_code.nim", "robot", false
load_enu_script "potato_code.nim", "robot"
var clone_obj*: PotatoType
me.ctrl.create_new = proc() =
potato_cradle = clone_obj

View File

@ -1,10 +0,0 @@
import enu/types, enu/class_macros
import macros except name
import pkg / print
import potato_class
const is_clone = true
load_enu_script "potato_code.nim", "robot", true
clone_obj = me

View File

@ -1,12 +1,7 @@
name potato(length = 5, width = 2, color = red, label = "hi", friendly = true)
assert length == 5
assert me.length == 5
when not is_clone:
assert potato.length == 5
assert potato.length == 5
me.length = 10
assert length == 10
potato.label = "red"
when not is_clone:
assert label == "red"
assert me.label == "red"
assert me.label == "red"

View File

@ -1,3 +1,10 @@
name bot1(named_property = "myprop")
echo "from bot1"
var b = bot1
forward 1
move me
back 1
register_active b

View File

@ -1,11 +1,12 @@
#name bot2
#var b = bot1.new(global = false)
echo "from bot2"
var b = bot1.new(global = false)
echo "from bot2 ", b.speed
assert b.named_property == "myprop"
echo b.named_property
b = bot1.new(named_property = "testing")
# TODO: verify this works:
# assert b.named_property == "testing"
assert bot1_cradle.named_property == "testing"
echo "passed"
assert b.named_property == "testing"
echo b.named_property
#
# echo "passed"

View File

@ -1,5 +1,8 @@
import std / [sugar, os]
import engine / [engine, contexts], core, models
import controllers/script_controllers, core, models
#import pkg / compiler / [vm, vmdef, options, lineinfos, ast]
import libs / [interpreters, eval]
var state = GameState.active
@ -8,21 +11,21 @@ state.logger = proc(level, msg: string) =
state.config.script_dir = current_source_path().parent_dir / "scripts" / "instancing"
state.config.lib_dir = current_source_path().parent_dir / ".." / ".." / "vmlib"
# state.config.lib_dir = current_source_path().parent_dir / ".." / ".." / "vmlib"
# var b = Bot.init
let controller = ScriptController.init
proc create(id: string): Bot =
result = Bot.init()
result.id = id
result.script_ctx = ScriptCtx.init
unit_ctxs[result.script_ctx.engine] = result
result.script_ctx.script = result.script_file
result.load_script()
let bot1 = create("bot_script_1")
state.units.add bot1
let bot2 = create("bot_script_2")
bot2.units[0].load_script
bot2.advance(0.1)
bot2.units[1].load_script
bot2.advance(0.1)
state.units.add bot2
#
# bot2.units[0].load_script
#
# bot2.advance(0.1)
# bot2.units[1].load_script
# bot2.advance(0.1)

View File

@ -1,6 +1,13 @@
import potato_class, potato_clone, enu/types
import potato_class, enu/types, pkg/print
import enu/base_api, enu/state_machine
var move_mode = 1
var me = ScriptNode()
let p = potato.new(label = "orange", color = green)
var target = p
forward 10
turn right
p.turn right
assert p.color == green
assert not p.is_nil
assert p of PotatoType

View File

@ -1,89 +1,151 @@
import random, types
import std / [random, strutils, math]
import types, state_machine
proc register_active*(self: ScriptNode) = discard
proc new_instance*(src, dest: ScriptNode) = discard
# API
proc quit*(exit_code = 0) = discard
proc sleep*(seconds = 1.0, ctx: Context = nil) = discard
proc create_new() = discard
proc stash() = discard
proc add_stashed() = discard
proc get_position(): Vector3 = discard
proc set_position(position: Vector3) = discard
proc get_rotation(): Vector3 = discard
proc collision(node: ScriptNode): Vector3 = discard
proc quit*(self: ScriptNode, exit_code = 0) = discard
proc sleep*(self: ScriptNode, seconds = 1.0, ctx: Context = nil) = discard
proc create_new*(self: ScriptNode) = discard
proc position*(self: ScriptNode): Vector3 = discard
proc `position=`*(self: ScriptNode, position: Vector3) = discard
proc rotation*(self: ScriptNode): Vector3 = discard
proc collision*(self: ScriptNode, node: ScriptNode): Vector3 = discard
var target: ScriptNode = me
proc action_running(self: ScriptNode): bool = discard
proc `action_running=`(self: ScriptNode, value: bool) = discard
proc add(node: ScriptNode) =
node.stash()
add_stashed()
proc yield_script(self: ScriptNode) = discard
proc distance(node: ScriptNode): float =
node.get_position().distance_to(get_position())
proc near(node: ScriptNode, less_than = 5.0): bool =
result = node.distance < less_than
proc far(node: ScriptNode, greater_than = 100.0): bool =
result = node.distance > greater_than
proc begin_move(self: ScriptNode, direction: Vector3, steps: float, move_mode: int) = discard
proc begin_turn(self: ScriptNode, axis: Vector3, steps: float, move_mode: int) = discard
proc echo_console(msg: string) = discard
proc echo(msg: varargs[string, `$`]) = echo_console msg.join
proc echo*(msg: varargs[string, `$`]) = echo_console msg.join
proc begin_move(direction: Vector3, steps: float, moving: int) = discard
proc begin_turn(axis: Vector3, steps: float, moving: int) = discard
#
# var target: ScriptNode = me
#
# proc add(node: ScriptNode) =
# node.stash()
# add_stashed()
#
# proc distance(node: ScriptNode): float =
# node.get_position().distance_to(get_position())
#
# proc near(node: ScriptNode, less_than = 5.0): bool =
# result = node.distance < less_than
#
# proc far(node: ScriptNode, greater_than = 100.0): bool =
# result = node.distance > greater_than
#
# proc echo_console(msg: string) = discard
# proc echo(msg: varargs[string, `$`]) = echo_console msg.join
#
proc forward(steps = 1.0, ctx: Context = nil) = target.ctrl.begin_move(FORWARD, steps, me, move_mode)
proc back(steps = 1.0, ctx: Context = nil) = target.ctrl.begin_move(BACK, steps, me, move_mode)
proc left(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = target.ctrl.begin_move(LEFT, steps, me, move_mode)
proc right(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = target.ctrl.begin_move(RIGHT, steps, me, move_mode)
proc l(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = left(steps)
proc r(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = right(steps)
proc up(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = target.ctrl.begin_move(UP, steps, me, move_mode)
proc u(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = up(steps)
proc down(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = target.ctrl.begin_move(DOWN, steps, me, move_mode)
proc d(steps = 1.0, ctx: Context = nil): Direction {.discardable.} = down(steps)
template wait(self: ScriptNode, body: untyped) =
mixin action_running, `action_running=`, yield_script
`action_running=`(self, true)
body
while self.action_running and self.advance_state_machine():
self.yield_script()
proc set_global(global: bool) = discard
proc get_global(): bool = discard
template forward*(self: ScriptNode, steps = 1.0) =
mixin begin_move, wait
me.wait self.begin_move(FORWARD, steps, move_mode)
proc turn(direction: proc(steps = 1.0, ctx: Context = nil): Direction, degrees = 90.0) =
var axis = if direction == r: RIGHT
elif direction == right: RIGHT
elif direction == l: LEFT
elif direction == left: LEFT
else: Vector3()
template back*(self: ScriptNode, steps = 1.0) =
mixin begin_move, wait
me.wait self.begin_move(BACK, steps, move_mode)
if axis == Vector3():
axis = if direction == u: UP
elif direction == up: UP
elif direction == d: DOWN
elif direction == down: DOWN
else: Vector3()
template left*(self: ScriptNode, steps = 1.0) =
mixin begin_move, wait
me.wait self.begin_move(LEFT, steps, move_mode)
assert axis != Vector3(), "Invalid direction"
target.ctrl.begin_turn(axis, degrees, me, move_mode)
template right*(self: ScriptNode, steps = 1.0) =
mixin begin_move, wait
me.wait self.begin_move(RIGHT, steps, move_mode)
proc turn(degrees: float, ctx: Context = nil) =
template up*(self: ScriptNode, steps = 1.0) =
mixin begin_move, wait
me.wait self.begin_move(UP, steps, move_mode)
template down*(self: ScriptNode, steps = 1.0) =
mixin begin_move, wait
me.wait self.begin_move(DOWN, steps, move_mode)
template l*(self: ScriptNode, steps = 1.0) = self.left(steps)
template r*(self: ScriptNode, steps = 1.0) = self.right(steps)
template u*(self: ScriptNode, steps = 1.0) = self.up(steps)
template d*(self: ScriptNode, steps = 1.0) = self.down(steps)
template f*(self: ScriptNode, steps = 1.0) = self.forward(steps)
template b*(self: ScriptNode, steps = 1.0) = self.back(steps)
# proc set_global(global: bool) = discard
# proc get_global(): bool = discard
#
proc turn(self: ScriptNode, direction: Directions, degrees = 90.0, move_mode: int) =
let dir = case direction:
of Directions.forward, Directions.f: FORWARD
of Directions.back, Directions.b: BACK
of Directions.left, Directions.l: LEFT
of Directions.right, Directions.r: RIGHT
of Directions.up, Directions.u: UP
of Directions.down, Directions.d: DOWN
self.begin_turn(dir, degrees, move_mode)
proc turn(self: ScriptNode, degrees: float, move_mode: int) =
let degrees = floor_mod(degrees, 360)
if degrees <= 180:
turn right, degrees
self.turn right, degrees, move_mode
else:
let d = 180 - (degrees - 180)
turn left, 180 - (degrees - 180)
self.turn left, 180 - (degrees - 180), move_mode
proc t(direction: proc(steps = 1.0, ctx: Context = nil): Direction, degrees = 90.0) =
turn(direction, degrees)
template turn*(self: ScriptNode, direction: Directions, degrees = 90.0) =
mixin wait
me.wait turn(self, direction, degrees, move_mode)
proc f(steps = 1.0, ctx: Context = nil) = forward(steps)
proc b(steps = 1.0, ctx: Context = nil) = back(steps)
template forward*(steps = 1.0) = target.forward(steps)
template back*(steps = 1.0) = target.back(steps)
template left*(steps = 1.0) = target.left(steps)
template right*(steps = 1.0) = target.right(steps)
template up*(steps = 1.0) = target.up(steps)
template down*(steps = 1.0) = target.down(steps)
proc look_at(node: ScriptNode) =
let
p1 = get_position()
p2 = node.get_position()
d = (p1 - p2).normalized()
let n = arctan2(d.x, d.z).rad_to_deg
let rot = get_rotation()
turn(left, n - rot.y)
template l*(steps = 1.0) = target.left(steps)
template r*(steps = 1.0) = target.right(steps)
template u*(steps = 1.0) = target.up(steps)
template d*(steps = 1.0) = target.down(steps)
template f*(steps = 1.0) = target.forward(steps)
template b*(steps = 1.0) = target.back(steps)
proc la(node: ScriptNode) = look_at(node)
template turn*(direction: Directions, degrees = 90.0) =
mixin wait
me.wait target.turn(direction, degrees, move_mode)
template turn*(degrees: float) =
mixin wait
me.wait target.turn(degrees, move_mode)
template move*[T: ScriptNode](new_target: T) =
target = new_target
#
# proc t(direction: proc(steps = 1.0, ctx: Context = nil): Direction, degrees = 90.0) =
# turn(direction, degrees)
#
# proc f(steps = 1.0, ctx: Context = nil) = forward(steps)
# proc b(steps = 1.0, ctx: Context = nil) = back(steps)
#
# proc look_at(node: ScriptNode) =
# let
# p1 = get_position()
# p2 = node.get_position()
# d = (p1 - p2).normalized()
# let n = arctan2(d.x, d.z).rad_to_deg
# let rot = get_rotation()
# turn(left, n - rot.y)
#
# proc la(node: ScriptNode) = look_at(node)

View File

@ -1,82 +1,72 @@
import std / [macros, strformat, strutils, sugar, sequtils, genasts]
import types
import types, base_api
proc params_to_vars(nodes: seq[NimNode]): NimNode =
proc params_to_assignments(target: NimNode, nodes: seq[NimNode]): NimNode =
result = new_stmt_list()
let empty = new_empty_node()
var vars = nnkVarSection.new_nim_node
var assign = new_stmt_list()
for node in nodes:
let prop = node[0]
let value = node[1]
result.add quote do:
`target`.`prop` = `value`
proc params_to_ident_defs(nodes: seq[NimNode]): seq[NimNode] =
for node in nodes:
let node = node.copy_nim_tree
let prop = node[0]
if prop.str_val notin ["global", "speed", "color"]:
if node.kind == nnkExprEqExpr:
vars.add nnkIdentDefs.new_tree(node[0], empty, node[1])
result.add nnkIdentDefs.new_tree(node[0], new_empty_node(), node[1])
elif node.kind == nnkExprColonExpr:
vars.add nnkIdentDefs.new_tree(node[0], node[1], empty)
result.add nnkIdentDefs.new_tree(node[0], node[1], new_empty_node())
else:
error("expected `my_param = 1`, `my_param: int` kind: " & $node.kind, node)
else:
proc params_to_properties(nodes: seq[NimNode]): NimNode =
result = new_nim_node(kind = nnkRecList)
let empty = new_empty_node()
for node in nodes:
let node = node.copy_nim_tree
let prop = node[0]
if prop.str_val notin ["global", "speed", "color"]:
if node.kind == nnkExprEqExpr:
result.add nnkIdentDefs.new_tree(node[0].postfix("*"), new_call(ident"type", node[1]), empty)
elif node.kind == nnkExprColonExpr:
result.add nnkIdentDefs.new_tree(node[0].postfix("*"), node[1], empty)
else:
error("expected `my_param = 1`, `my_param: int` kind: " & $node.kind, node)
assign.add new_assignment(prop.copy_nim_node, node[1].copy_nim_node)
result.add vars
result.add assign
proc var_types(vars: NimNode): seq[tuple[name, typ: string]] =
for sect in vars:
if sect.kind == nnkVarSection:
for node in sect:
let t = node[2]
let typ = if t.kind in nnkLiterals:
if t.kind in nnkStrLit..nnkTripleStrLit:
"string"
else:
($t.kind)[3..^4].to_lower_ascii
elif t == ident"true" or t == ident"false":
"bool"
elif t.kind == nnkEmpty:
node[1].str_val
else:
error("Don't know how to create getter/setter for " & t.repr, t)
return
result.add (node[0].str_val, typ)
proc build_ctors(name_str: string, type_name, cradle_name: NimNode, params: seq[NimNode]): NimNode =
proc build_ctors(name_str: string, type_name: NimNode, params: seq[NimNode]): NimNode =
var ctor_body = quote do:
instance.ctrl.create_new()
result = `cradle_name`
result = `type_name`()
new_instance(instance, result)
# TODO: ident normalization
let vars = params_to_vars(params)
let var_names = var_types(vars).map_it it.name
for name in var_names:
let setter = ident("set_" & name)
let named_var = ident(name)
let stmt = quote do:
result.user_ctrl.`setter`(`named_var`)
ctor_body.add(stmt)
for param in params:
let prop = param[0]
ctor_body.add quote do:
result.`prop` = `prop`
let vars = params_to_ident_defs(params)
let var_names = vars.map_it $it[0]
let instance_def = new_ident_defs("instance".ident, type_name)
var params = @[type_name] & instance_def & vars[0][0..^1]
var params = @[type_name] & instance_def & vars
var global = "global".ident
if "global" notin var_names:
params &= new_ident_defs(global, new_empty_node(), ident("global_default"))
ctor_body.add quote do:
result.ctrl.set_global(`global`)
result.global = `global`
var speed = "speed".ident
if "speed" notin var_names:
params &= new_ident_defs(speed, new_empty_node(), new_float_lit_node(1.0))
ctor_body.add quote do:
result.ctrl.set("speed", `speed`)
result.speed = `speed`
var color = "color".ident
if "color" notin var_names:
params &= new_ident_defs(color, new_empty_node(), bind_sym"eraser")
ctor_body.add quote do:
result.ctrl.set_color(`color`)
result.color = `color`
# add baked in constructor params for speed, color, etc.
# probably shouldn't be here.
@ -87,62 +77,6 @@ proc build_ctors(name_str: string, type_name, cradle_name: NimNode, params: seq[
body = ctor_body
)
proc build_accessors(vars: NimNode): NimNode =
result = new_stmt_list()
for (name, typ) in var_types(vars):
let var_name = name.ident
let typ = typ.ident
let setter_name = ("set_" & name).ident
let getter_name = ("get_" & name).ident
result.add quote do:
me.user_ctrl.`getter_name` = proc(): `typ` =
`var_name`
me.user_ctrl.`setter_name` = proc(val: `typ`) =
`var_name` = val
proc build_public_interface(vars, type_name: NimNode): NimNode =
result = new_stmt_list()
for (name, typ) in var_types(vars):
let
getter_name = name.ident
setter_name = ident(name & "=")
getter_accessor = ident("get_" & name)
setter_accessor = ident("set_" & name)
typ = typ.ident
result.add quote do:
proc `getter_name`*(self: `type_name`): `typ` =
self.user_ctrl.`getter_accessor`()
proc `setter_name`*(self: `type_name`, val: `typ`) =
self.user_ctrl.`setter_accessor`(val)
proc build_controller(name_str: string, type_name, vars: NimNode): NimNode =
result = quote do:
type
`type_name`* = ref object
getter*: proc():int
setter*: proc(val {.inject.}: int)
var fields = result[0][2][0][2]
var getter = fields[0].copy_nim_tree
var setter = fields[1].copy_nim_tree
fields.del(0)
fields.del(0)
for (name, typ) in var_types(vars):
var getter = getter.copy_nim_tree
var setter = setter.copy_nim_tree
echo "NAME: ", name, " ", typ.ident
getter[0][1] = ident("get_" & name)
getter[1][0][0] = typ.ident
setter[0][1] = ident("set_" & name)
setter[1][0][1][1] = typ.ident
fields.add getter
fields.add setter
proc extract_class_info(name_node: NimNode): tuple[name: string, params: seq[NimNode]] =
result = if name_node.kind == nnkIdent:
(name_node.str_val, @[])
@ -153,49 +87,30 @@ proc extract_class_info(name_node: NimNode): tuple[name: string, params: seq[Nim
error("expected `name my_name` or `name my_name(my_param1 = 1, my_param2 = 2, ...)`", name_node)
return
proc build_class(name_node: NimNode, is_clone: bool): NimNode =
proc build_class(name_node: NimNode): NimNode =
let base_class = ident"ScriptNode"
let (name, params) = extract_class_info(name_node)
let
type_name = (name & "Type").to_upper_ascii.nim_ident_normalize.ident
var_name = name.ident
cradle_name = (name & "_cradle").to_lower_ascii.nim_ident_normalize.ident
clone_name = name & "_clone"
vars = params_to_vars(params)
ctors = build_ctors(name, type_name, cradle_name, params)
ctors = build_ctors(name, type_name, params)
controller_type = (name & "Controller").to_upper_ascii.nim_ident_normalize.ident
controller = build_controller(name, controller_type, vars)
iface = build_public_interface(vars, type_name)
result = new_stmt_list()
if is_clone:
result.add quote do:
let me {.inject.} = `type_name`(name: `clone_name`, ctrl: Controller(), user_ctrl: `controller_type`())
`cradle_name` = me
else:
let name_str = name
result.add quote do:
`controller`
type
`type_name`* = ref object of `base_class`
create_new*: proc()
user_ctrl*: `controller_type`
`iface`
let me {.inject.} = `type_name`(name: `name_str`, user_ctrl: `controller_type`(), ctrl: Controller())
let `var_name`* {.inject.} = me
var `cradle_name`*: `type_name`
`ctors`
proc build_vars_and_accessors(name_node: NimNode): NimNode =
let
(name, params) = extract_class_info(name_node)
vars = params_to_vars(params)
accessors = build_accessors(vars)
let name_str = name
var type_def = quote do:
type `type_name`* = ref object of `base_class`
result = gen_ast(vars, accessors):
vars
accessors
type_def[0][2][0][2] = params_to_properties(params)
result.add quote do:
`type_def`
let me {.inject.} = `type_name`(name: `name_str`)
register_active(me)
let `var_name`* {.inject.} = me
`ctors`
proc pop_name_node(ast: NimNode): NimNode =
let ident_name = "name"
@ -206,10 +121,8 @@ proc pop_name_node(ast: NimNode): NimNode =
ast.del(i)
break
macro load_enu_script*(file_name, include_name: string, is_clone: bool): untyped =
let
file_name = file_name.str_val
is_clone: bool = is_clone.bool_val
macro load_enu_script*(file_name, include_name: string): untyped =
let file_name = file_name.str_val
let ast = parse_stmt(file_name.static_read, file_name)
let name_node = pop_name_node(ast)
let include_file = quote do:
@ -217,14 +130,25 @@ macro load_enu_script*(file_name, include_name: string, is_clone: bool): untyped
result = new_stmt_list()
var inner = new_stmt_list()
if name_node.kind != nnkNilLit:
result.add build_class(name_node, is_clone)
let (name, params) = extract_class_info(name_node)
result.add build_class(name_node)
result.add include_file
inner.add build_vars_and_accessors(name_node)
inner.add params_to_assignments(ident"me", params)
else:
result.add quote do:
let me {.inject.} = ScriptNode(ctrl: Controller())
let me {.inject.} = ScriptNode()
register_active(me)
result.add include_file
inner.add ast
result.add new_block_stmt(inner)
result.add quote do:
#proc run_script*(me {.inject.}: me.type) =
var target {.inject.}: ScriptNode = me
include loops
`inner`
#run_script(me)
system.echo "code: "
system.echo result.repr

View File

@ -1,39 +1,11 @@
import state_machine, types
var context: Context
var
context: Context
proc yield_script = discard
var load_values: proc()
proc advance_state_machine(): bool =
me.advance_state_machine = proc(): bool =
result = if not context.is_nil:
let val = context.advance()
if load_values != nil:
load_values()
val
context.advance()
else:
true
proc set_action_running*(running: bool) =
me.ctrl.action_running = running
template wait(node: ScriptNode, body: untyped) =
node.ctrl.action_running = true
when defined(nimscript):
body
while node.ctrl.action_running and node.ctrl.advance_state_machine():
node.ctrl.yield_script()
if load_values != nil:
load_values()
else:
# only for tests
var counter = 0
while counter < 3 and advance_state_machine():
inc counter
body
proc loop_started(ctx: Context, main_loop: bool) =
if main_loop:
context = ctx

View File

@ -0,0 +1,2 @@
import base_api
echo "IMPORTED!!!"

View File

@ -1,25 +1,24 @@
import types
let player* = PlayerType(ctrl: Controller())
let player* = PlayerType()
let me = player
proc quit*(exit_code = 0) = discard
proc get_position(): Vector3 = discard
proc get_rotation(): Vector3 = discard
proc set_velocity(v: Vector3) = discard
proc get_velocity(): Vector3 = discard
me.ctrl.get_position = proc(): Vector3 =
get_position()
me.ctrl.get_rotation = proc(): Vector3 =
get_rotation()
me.ctrl.get_velocity = proc(): Vector3 =
get_velocity()
me.ctrl.set_velocity = proc(v: Vector3) =
set_velocity(v)
# me.ctrl.get_position = proc(): Vector3 =
# get_position()
#
# me.ctrl.get_rotation = proc(): Vector3 =
# get_rotation()
#
# me.ctrl.get_velocity = proc(): Vector3 =
# get_velocity()
#
# me.ctrl.set_velocity = proc(v: Vector3) =
# set_velocity(v)
proc bounce*(me: PlayerType, power = 1.0) =
me.velocity = me.velocity + UP * power * 30

View File

@ -1,77 +1,12 @@
import helpers, strutils, types, math
export helpers
include loops
let skip_3d = true
var
speed* = 1.0
scale* = 1.0
color*: ColorIndex
height* = 0.0
position* = vec3(0, 0, 0)
move_mode = 0
include base_api
load_values = proc() =
position = me.position
height = position.y
me.ctrl.begin_move = proc(direction: Vector3, steps: float, self: ScriptNode, moving: int) =
self.wait begin_move(direction, steps, 0)
me.ctrl.begin_turn = proc(axis: Vector3, degrees: float, self: ScriptNode, moving: int) =
self.wait begin_turn(axis, degrees, 0)
me.ctrl.advance_state_machine = proc(): bool = advance_state_machine()
me.ctrl.yield_script = proc() = yield_script()
me.ctrl.set_global = proc(global: bool) =
set_global(global)
me.ctrl.get_global = proc(): bool =
get_global()
me.ctrl.set = proc(name: string, new_speed:float) =
speed = new_speed
me.ctrl.get = proc(name: string): float = speed
me.ctrl.stash = proc() =
stash()
me.ctrl.get_position = proc(): Vector3 =
get_position()
me.ctrl.set_position = proc(position: Vector3) =
set_position(position)
me.ctrl.get_rotation = proc(): Vector3 =
get_rotation()
me.ctrl.look_at = proc(target: ScriptNode) =
look_at(target)
me.ctrl.add_stashed = proc() =
add_stashed()
me.ctrl.create_new = proc() =
create_new()
me.ctrl.get_color = proc(): ColorIndex = color
me.ctrl.set_color = proc(value: ColorIndex) = color = value
proc play*(animation_name: string) = discard
proc set_speed*(spd: float) = speed = spd
var move_mode = 1
import base_api
proc walk*() =
speed = 1.0
play("walk")
me.speed = 1.0
#me.play("walk")
proc run*() =
speed = 5.0
play("run")
me.speed = 5.0
#me.play("run")
proc move*[T: ScriptNode](new_target: T) =
target = new_target

View File

@ -1,15 +1,5 @@
import macros, strformat, strutils, helpers, sequtils, tables
type
Context* = ref object
stack: seq[Frame]
Frame = ref object
manager: proc(active: bool):bool
action: proc()
Halt = object of CatchableError
Loop = ref object
states: Table[string, NimNode]
from_states: seq[(string, NimNode)]
import std / [macros, strformat, strutils, sequtils, tables]
import types, helpers
proc current_loop(value: Loop = nil): Loop =
var loop {.global.}: Loop

View File

@ -11,30 +11,37 @@ type
id: int
name*: string
Controller* = object
action_running*: bool
advance_state_machine*: proc(): bool
yield_script*: proc()
begin_move*: proc(direction: Vector3, steps: float, self: ScriptNode, moving: int)
begin_turn*: proc(axis: Vector3, degrees: float, self: ScriptNode, moving: int)
look_at*: proc(target: ScriptNode)
set*: proc(var_name: string, value: float)
get*: proc(var_name: string): float
create_new*: proc()
set_color*: proc(value: ColorIndex)
get_color*: proc(): ColorIndex
get_position*: proc(): Vector3
set_position*: proc(position: Vector3)
get_rotation*: proc(): Vector3
set_velocity*: proc(velocity: Vector3)
get_velocity*: proc(): Vector3
set_global*: proc(global: bool)
get_global*: proc(): bool
stash*: proc()
add_stashed*: proc()
Directions* = enum
up, u, down, d, left, l, right, r, forward, f, back, b
# Controller* = object
# action_running*: bool
# advance_state_machine*: proc(): bool
# yield_script*: proc()
# begin_move*: proc(direction: Vector3, steps: float, self: ScriptNode, moving: int)
# begin_turn*: proc(axis: Vector3, degrees: float, self: ScriptNode, moving: int)
# look_at*: proc(target: ScriptNode)
# set*: proc(var_name: string, value: float)
# get*: proc(var_name: string): float
# create_new*: proc()
# set_color*: proc(value: ColorIndex)
# get_color*: proc(): ColorIndex
# get_position*: proc(): Vector3
# set_position*: proc(position: Vector3)
# get_rotation*: proc(): Vector3
# set_velocity*: proc(velocity: Vector3)
# get_velocity*: proc(): Vector3
# set_global*: proc(global: bool)
# get_global*: proc(): bool
# stash*: proc()
# add_stashed*: proc()
ScriptNode* = ref object of Node
ctrl*: Controller
color*: ColorIndex
speed*: float
global*: bool
velocity*: Vector3
advance_state_machine*: proc(): bool
ColorIndex* = enum
eraser = 0,
@ -47,10 +54,21 @@ type
Energy* = range[0.0..100.0]
Direction* = object
PlayerType* = ref object of ScriptNode
Context* = ref object
stack*: seq[Frame]
Frame* = ref object
manager*: proc(active: bool):bool
action*: proc()
Halt* = object of CatchableError
Loop* = ref object
states*: Table[string, NimNode]
from_states*: seq[(string, NimNode)]
proc vec3*(x, y, z: float): Vector3 {.inline.} =
Vector3(x:x, y:y, z:z)
@ -312,84 +330,66 @@ proc moveToward*(vFrom, to: Vector3, delta: float32): Vector3 =
converter vec3_to_bool*(v: Vector3): bool =
v != vec3(0, 0, 0)
template forward*(target: ScriptNode, steps = 1.0) =
target.ctrl.begin_move(FORWARD, steps, self)
template back*(target: ScriptNode, steps = 1.0) =
target.ctrl.begin_move(BACK, steps, self)
template left*(target: ScriptNode, steps = 1.0) =
target.ctrl.begin_move(LEFT, steps, self)
template right*(target: ScriptNode, steps = 1.0) =
target.ctrl.begin_move(RIGHT, steps, self)
template turn_left*(target: ScriptNode, degrees = 90.0) =
target.ctrl.begin_turn(LEFT, degrees, self)
template turn_right*(target: ScriptNode, degrees = 90.0) =
target.ctrl.begin_turn(RIGHT, degrees, self)
proc `speed=`*(self: ScriptNode, speed: float) =
self.ctrl.set("speed", speed)
proc speed*(self: ScriptNode): float =
self.ctrl.get("speed")
proc `color=`*(self: ScriptNode, color: ColorIndex) =
self.ctrl.set_color(color)
proc color*(self: ScriptNode): ColorIndex =
self.ctrl.get_color()
proc `global=`*(self: ScriptNode, global: bool) =
self.ctrl.set_global(global)
proc global*(self: ScriptNode): bool =
self.ctrl.get_global()
proc `velocity=`*(self: PlayerType, v: Vector3) =
self.ctrl.set_velocity(v)
proc velocity*(self: PlayerType): Vector3 =
self.ctrl.get_velocity()
proc position*(self: ScriptNode): Vector3 =
self.ctrl.get_position()
proc `position=`*(self: ScriptNode, position: Vector3) =
self.ctrl.set_position(position)
template up*(target: ScriptNode, steps = 1.0) =
target.ctrl.begin_move(UP, steps, me)
template down*(target: ScriptNode, steps = 1.0) =
target.ctrl.begin_move(DOWN, steps, me)
template turn_up*(target: ScriptNode, degrees = 90.0) =
target.ctrl.begin_turn(UP, degrees, me)
template turn_down*(target: ScriptNode, degrees = 90.0) =
target.ctrl.begin_turn(DOWN, degrees, me)
proc look_at*(actor: ScriptNode) =
actor.ctrl.look_at(actor)
proc near*(actor, target: ScriptNode, less_than = 5.0): bool =
let distance = actor.ctrl.get_position().distance_to(target.ctrl.get_position())
result = distance < less_than
proc get_position*(actor: ScriptNode): Vector3 =
actor.ctrl.get_position()
proc set_position*(actor: ScriptNode, position: Vector3) =
actor.ctrl.set_position(position)
proc get_rotation*(actor: ScriptNode): Vector3 =
actor.ctrl.get_rotation()
proc stash*(actor: ScriptNode) =
actor.ctrl.stash()
proc add_stashed*(actor: ScriptNode) =
actor.ctrl.add_stashed()
# proc `speed=`*(self: ScriptNode, speed: float) =
# self.ctrl.set("speed", speed)
#
# proc speed*(self: ScriptNode): float =
# self.ctrl.get("speed")
#
# proc `color=`*(self: ScriptNode, color: ColorIndex) =
# self.ctrl.set_color(color)
#
# proc color*(self: ScriptNode): ColorIndex =
# self.ctrl.get_color()
#
# proc `global=`*(self: ScriptNode, global: bool) =
# self.ctrl.set_global(global)
#
# proc global*(self: ScriptNode): bool =
# self.ctrl.get_global()
#
# proc `velocity=`*(self: PlayerType, v: Vector3) =
# self.ctrl.set_velocity(v)
#
# proc velocity*(self: PlayerType): Vector3 =
# self.ctrl.get_velocity()
#
# proc position*(self: ScriptNode): Vector3 =
# self.ctrl.get_position()
#
# proc `position=`*(self: ScriptNode, position: Vector3) =
# self.ctrl.set_position(position)
#
# template up*(target: ScriptNode, steps = 1.0) =
# target.ctrl.begin_move(UP, steps, me)
#
# template down*(target: ScriptNode, steps = 1.0) =
# target.ctrl.begin_move(DOWN, steps, me)
#
# template turn_up*(target: ScriptNode, degrees = 90.0) =
# target.ctrl.begin_turn(UP, degrees, me)
#
# template turn_down*(target: ScriptNode, degrees = 90.0) =
# target.ctrl.begin_turn(DOWN, degrees, me)
#
# proc look_at*(actor: ScriptNode) =
# actor.ctrl.look_at(actor)
#
# proc near*(actor, target: ScriptNode, less_than = 5.0): bool =
# let distance = actor.ctrl.get_position().distance_to(target.ctrl.get_position())
# result = distance < less_than
#
# proc get_position*(actor: ScriptNode): Vector3 =
# actor.ctrl.get_position()
#
# proc set_position*(actor: ScriptNode, position: Vector3) =
# actor.ctrl.set_position(position)
#
# proc get_rotation*(actor: ScriptNode): Vector3 =
# actor.ctrl.get_rotation()
#
# proc stash*(actor: ScriptNode) =
# actor.ctrl.stash()
#
# proc add_stashed*(actor: ScriptNode) =
# actor.ctrl.add_stashed()