Instancing

This commit is contained in:
Scott Wadden 2021-06-18 02:47:02 -03:00
parent 3a8a7b21aa
commit 5f24673be2
32 changed files with 696 additions and 498 deletions

View File

@ -1,17 +1,19 @@
import strformat, strutils, os, json
let
var
(target, lib_ext, exe_ext) = case host_os
of "windows": ("windows", ".dll", ".exe")
of "macosx" : ("osx", ".dylib", "")
else : ("x11", ".so", "")
generated_dir = "godotapi"
generated_dir = "generated/godotapi"
api_json = "api.json"
generator = "tools/build_helpers"
godot_bin = this_dir() & &"/vendor/godot/bin/godot.{target}.opt.tools.64{exe_ext}"
godot_build_url = "https://docs.godotengine.org/en/stable/development/compiling/index.html"
gcc_dlls = ["libgcc_s_seh-1.dll", "libwinpthread-1.dll"]
nim_dlls = ["pcre64.dll"]
godot_opts = "target=release_debug"
generator_path = ""
version = "0.1.99"
author = "Scott Wadden"
@ -27,19 +29,20 @@ requires "nim >= 1.4.0",
"https://github.com/dsrw/Nim#1d5093d",
"cligen 1.2.2",
"json_serialization",
"print"
"print >= 1.0"
var
godot_opts = "target=release_debug"
proc gen: string =
if generator_path == "":
exec &"nimble c {generator}"
generator_path = find_exe generator
generator_path
task build_godot, "Build godot":
mk_dir generated_dir
exec "git submodule update --init"
exec &"nimble c {generator}"
let
gen = find_exe generator
scons = find_exe "scons"
cores = gorge gen & " core_count"
cores = gorge(gen() & " core_count")
if scons == "":
quit &"*** scons not found on path, and is required to build Godot. See {godot_build_url} ***"
with_dir "vendor/godot":
@ -51,10 +54,9 @@ proc find_and_copy_dlls(dep_path, dest: string, dlls: varargs[string]) =
task prereqs, "Generate Godot API binding":
build_godot_task()
let gen = find_exe generator
exec &"{godot_bin} --gdnative-generate-json-api {join_path generated_dir, api_json}"
exec &"{gen} generate_api -d={generated_dir} -j={api_json}"
exec &"{gen} copy_stdlib -d=vmlib/stdlib"
exec &"{gen()} generate_api -d={generated_dir} -j={api_json}"
exec &"{gen()} copy_stdlib -d=vmlib/stdlib"
if host_os == "windows":
# Assumes mingw
find_and_copy_dlls find_exe("gcc").parent_dir, join_path("app", "_dlls"), gcc_dlls
@ -74,14 +76,16 @@ task start, "Run Enu":
cd "app"
exec godot_bin & " --verbose scenes/game.tscn"
task gen, "Generate build_helpers":
discard gen()
proc code_sign(id, path: string) =
exec &"codesign -s '{id}' -v --timestamp --options runtime {path}"
task dist, "Build distribution":
let release_bin = &"vendor/godot/bin/godot.{target}.opt.64{exe_ext}"
prereqs_task()
let gen = find_exe generator
exec &"{gen} write_export_presets --enu_version {version}"
exec &"{gen()} write_export_presets --enu_version {version}"
godot_opts = "target=release tools=no"
build_godot_task()
rm_dir "dist"
@ -90,7 +94,7 @@ task dist, "Build distribution":
let config = read_file("dist_config.json").parse_json
mkdir "dist"
exec "cp -r installer/Enu.app dist/Enu.app"
exec &"{gen} write_info_plist --enu_version {version}"
exec &"{gen()} write_info_plist --enu_version {version}"
exec "mkdir -p dist/Enu.app/Contents/MacOS"
exec "mkdir -p dist/Enu.app/Contents/Frameworks"
exec &"cp {release_bin} dist/Enu.app/Contents/MacOS/Enu"

View File

@ -5,5 +5,6 @@
--threads:on
--tlsEmulation:off
--threadAnalysis:off
#--define:gcDestructors
#--gc:orc
switch("path", this_dir())
switch("path", this_dir() & "/../generated")

View File

@ -14,6 +14,8 @@ var
section_start_time: MonoTime
current_proc: string
template nim_filename*: string = instantiation_info(full_paths = true).filename
template trace*(body: untyped): untyped =
# https://github.com/nim-lang/Nim/issues/8212#issuecomment-657202258
when not declaredInScope(internalCProcName):

179
src/engine/contexts.nim Normal file
View File

@ -0,0 +1,179 @@
import std / [monotimes, os, hashes, sets, strutils]
import core, globals, engine/engine, types
import godot, compiler/idgen
import godotapi / [node]
export engine
type
Callback* = proc(delta: float): bool
ScriptCtx* = ref object
speed: float
script*: string
engine*: Engine
timer: MonoTime
prefix: string
paused: bool
id: int
load_vars*: proc()
reload_script: proc()
using self: auto
const ADVANCE_STEP = 0.5.seconds
var
module_names: HashSet[string]
active_node: Node
ctxs: Table[Engine, ScriptCtx]
failed: seq[tuple[ctx: ScriptCtx, ex: ref VMQuit]]
starting = true
modules_to_load: HashSet[ScriptCtx]
template ctx: untyped = self.script_ctx
proc hash*(s: ScriptCtx): Hash = s.id.hash
proc init*(t: typedesc[ScriptCtx], prefix: string): ScriptCtx =
let e = Engine.init()
result = ScriptCtx(id: get_id(), engine: e, timer: MonoTime.high, prefix: prefix)
ctxs[e] = result
modules_to_load.incl result
proc current_ctx*(): ScriptCtx = ctxs[current_engine()]
proc is_script_loadable*(self): bool =
ctx.script != "none" and ctx.script.file_exists
proc engine*(self): Engine = ctx.engine
proc script*(self): string = ctx.script
proc paused*(self): bool = ctx.paused
proc `paused=`*(self; paused: bool) = ctx.paused = paused
proc speed*(self): float = ctx.speed
proc `speed=`*(self; speed: float) = ctx.speed = speed
proc set_script*(self) =
let path = ctx.prefix & &"_{self.script_index}.nim"
ctx.script = join_path(config.script_dir, path)
proc start_advance_timer*(ctx: ScriptCtx) =
ctx.timer = get_mono_time() + ADVANCE_STEP
proc advance*(self; delta: float64) =
let now = get_mono_time()
active_node = self
let e = ctx.engine
if e.callback == nil or (not e.callback(delta)):
ctx.timer = MonoTime.high
discard e.call_proc("set_action_running", e.module_name, false)
self.update_running_state ctx.engine.resume()
elif now >= ctx.timer:
ctx.timer = now + ADVANCE_STEP
e.saved_callback = e.callback
e.callback = nil
discard e.resume()
proc load_vars*(self) =
let self_name = if self.is_nil: "nil" else: self.name
let active_name = if active_node.is_nil: "nil" else: active_node.name
let e = ctx.engine
when compiles(self.on_load_vars):
self.on_load_vars()
proc begin_move(self; direction: Vector3, steps: float): bool =
self.load_vars()
current_engine().callback = self.on_begin_move(direction, steps)
result = not current_engine().callback.is_nil
proc begin_turn(self; direction: Vector3, degrees: float): bool =
self.load_vars()
current_engine().callback = self.on_begin_turn(direction, degrees)
result = not current_engine().callback.is_nil
proc error*(e: Engine, ex: ref VMQuit) =
e.running = false
when defined(enu_simulate):
raise ex
else:
err ex.msg
trigger("script_error")
proc retry_failed_scripts =
# We don't know what order scripts will load in.
# Once all scripts have been loaded, retry the
# failures. Keep retrying until the list of failures
# stops changing, then print any remaining errors.
starting = false
var cycling = true
while cycling:
let prev_failed = failed
failed = @[]
for f in prev_failed:
f.ctx.reload_script()
if prev_failed == failed:
cycling = false
for f in failed:
f.ctx.engine.error(f.ex)
proc load_script*(self; script = "", retry_failed = true) =
modules_to_load.excl ctx
defer:
if modules_to_load.card == 0 and retry_failed:
retry_failed_scripts()
if script != "":
ctx.script = script
ctx.reload_script = proc() =
self.load_script(retry_failed = false)
ctx.engine.callback = nil
try:
if not self.is_script_loadable:
return
if not ctx.paused:
let module_name = ctx.script.split_file.name
module_names.incl module_name
var others = module_names
others.excl module_name
let imports = if others.card > 0:
"import " & others.to_seq.join(", ")
else:
""
let code = self.code_template(module_name & ".nim", imports)
let initialized = ctx.engine.initialized
ctx.load_vars = proc() = self.load_vars()
ctx.engine.load(config.script_dir, ctx.script, code, config.lib_dir)
if not initialized:
with ctx.engine:
expose "yield_script", proc(a: VmArgs):bool =
current_engine().callback = current_engine().saved_callback
current_engine().saved_callback = nil
true
expose("begin_move", a => self.begin_move(get_vec3(a, 0), get_float(a, 1)))
expose("begin_turn", a => self.begin_turn(get_vec3(a, 0), get_float(a, 1)))
expose("echo_console", a => echo_console(get_string(a, 0)))
expose "sleep_impl", proc(a: VmArgs): bool =
let seconds = get_float(a, 0)
var duration = 0.0
when compiles(self.on_sleep):
self.on_sleep(seconds)
current_engine().callback = proc(delta: float): bool =
duration += delta
return duration < seconds
true
expose "quit", proc(a: VmArgs): bool =
let e = current_engine()
e.exit_code = some(a.get_int(0).int)
e.pause()
e.running = false
result = false
self.on_script_loaded(ctx.engine)
if not ctx.paused:
self.update_running_state ctx.engine.run()
except VMQuit as e:
if starting:
failed.add (ctx, e)
else:
self.engine.error(e)

View File

@ -1,7 +1,8 @@
import compiler / [vm, vmdef, options, lineinfos, ast]
import eval
import os, strformat, std/with, parseutils
import ../core
import compiler / [vm, vmdef, options, lineinfos, ast, idgen]
import types, eval
import std / [os, strformat, with, parseutils, hashes]
import core
import godot
export Interpreter
export VmArgs, get_float, get_int, get_string, get_bool
@ -23,8 +24,13 @@ type
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
id: int
running*: bool
const
STDLIB_PATHS = (w". core pure pure/collections pure/concurrency" &
@ -33,11 +39,16 @@ const
var
interpreter: Interpreter
current_engine: Engine
current: Engine
proc current(): Engine = current_engine
proc hash*(e: Engine): Hash = e.id.hash
proc current_engine*(): Engine = current
proc set_current(e: Engine) =
current_engine = e
current = e
proc init*(t: typedesc[Engine]): Engine =
result = Engine()
result.id = get_id()
proc init_interpreter(script_dir, vmlib: string) =
trace:
@ -47,8 +58,8 @@ proc init_interpreter(script_dir, vmlib: string) =
log_trace("create_interpreter")
with interpreter:
register_error_hook proc(config, info, msg, severity: auto) {.gcsafe.} =
let e = current_engine
if severity == Error and config.error_counter >= config.error_max:
let e = current_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:
@ -60,7 +71,7 @@ proc init_interpreter(script_dir, vmlib: string) =
raise (ref VMQuit)(info: info, msg: msg)
register_enter_hook proc(c, pc, tos, instr: auto) =
let e = current()
let e = current_engine()
let info = c.debug[pc]
if info.file_index.int == 0 and e.previous_line != info:
if e.line_changed != nil:
@ -77,19 +88,16 @@ proc init_interpreter(script_dir, vmlib: string) =
log_trace("hooks")
proc pause*(e: Engine) =
set_current e
e.pause_requested = true
proc load*(e: Engine, script_dir, module_name, code, vmlib: string) =
proc load*(e: Engine, script_dir, file_name, code, vmlib: string) =
e.code = code
e.module_name = module_name
e.file_name = file_name
e.module_name = file_name.split_file.name
set_current e
if interpreter.is_nil:
init_interpreter(script_dir, vmlib)
e.is_main_module = true
interpreter.implement_routine "*", e.module_name, "quit", proc(a: VmArgs) {.gcsafe.} =
e.exit_code = some(a.get_int(0).int)
e.pause()
e.initialized = true
proc run*(e: Engine): bool =
@ -97,26 +105,48 @@ proc run*(e: Engine): bool =
e.exit_code = none(int)
e.errors = @[]
try:
interpreter.load_module(e.module_name, e.code)
interpreter.load_module(e.file_name, e.code)
false
except VMPause:
e.exit_code.is_none
proc to_node*(val: int): PNode =
new_int_node(nk_int_lit, val)
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)
proc to_node*(val: float): PNode =
new_float_node(nk_float_lit, val)
# 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*(val: string): PNode =
new_str_node(nk_str_lit, val)
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*(val: bool): PNode =
let v = if val: 1 else: 0
new_int_node(nk_int_lit, v)
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.}=
set_current e
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}'")
@ -134,7 +164,7 @@ 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):
e.pause()
current_engine().pause()
proc call_float*(e: Engine, proc_name: string): float =
e.call_proc(proc_name).get_float()
@ -153,6 +183,9 @@ 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

View File

@ -1,19 +0,0 @@
import std/monotimes
import ".." / [core, engine/engine]
const ADVANCE_STEP = 0.5.seconds
proc start_advance_timer*(self: auto) =
self.advance_timer = get_mono_time() + ADVANCE_STEP
proc advance*(self: auto, delta: float64) =
let now = get_mono_time()
if self.callback == nil or not self.callback(delta):
self.advance_timer = MonoTime.high
discard self.engine.call_proc("set_action_running", self.engine.module_name, false)
self.update_running_state self.engine.resume()
elif now >= self.advance_timer:
self.advance_timer = now + ADVANCE_STEP
self.saved_callback = self.callback
self.callback = nil
discard self.engine.resume()

2
src/engine/types.nim Normal file
View File

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

View File

@ -1,7 +1,7 @@
import ../godotapi / [input, input_event, gd_os, node, scene_tree, viewport_container,
packed_scene, resource_saver, sprite, control, viewport,
performance, label, theme, dynamic_font, resource_loader, main_loop,
gd_os, project_settings, input_map, input_event, input_event_action]
import godotapi / [input, input_event, gd_os, node, scene_tree, viewport_container,
packed_scene, resource_saver, sprite, control, viewport,
performance, label, theme, dynamic_font, resource_loader, main_loop,
gd_os, project_settings, input_map, input_event, input_event_action]
import godot, threadpool, times, os, json_serialization
import core, globals
@ -152,58 +152,58 @@ gdobj Game of Node:
echo &"loaded {config.scene}"
method ready* =
trace:
self.viewport_container = self.get_node("ViewportContainer").as(ViewportContainer)
self.scene_packer = gdnew[PackedScene]()
self.load_world()
self.get_tree().set_auto_accept_quit(false)
assert not self.viewport_container.is_nil
state.game = self
let (theme_holder, theme) = if hostOS == "macosx":
( self.find_node("ThemeHolder").as(Container),
load("res://themes/AppleTheme.tres").as(Theme))
else:
let node = self.find_node("Panels").as(Container)
(node, node.theme)
let
font = theme.default_font.as(DynamicFont)
bold_font = theme.get_font("bold_font", "RichTextLabel")
.as(DynamicFont)
self.viewport_container = self.get_node("ViewportContainer").as(ViewportContainer)
self.scene_packer = gdnew[PackedScene]()
self.load_world()
self.get_tree().set_auto_accept_quit(false)
assert not self.viewport_container.is_nil
state.game = self
let (theme_holder, theme) = if hostOS == "macosx":
( self.find_node("ThemeHolder").as(Container),
load("res://themes/AppleTheme.tres").as(Theme))
else:
let node = self.find_node("Panels").as(Container)
(node, node.theme)
let
font = theme.default_font.as(DynamicFont)
bold_font = theme.get_font("bold_font", "RichTextLabel")
.as(DynamicFont)
font.size = config.font_size
bold_font.size = config.font_size
theme_holder.theme = theme
self.shrink = config.downscale
font.size = config.font_size
bold_font.size = config.font_size
theme_holder.theme = theme
self.shrink = config.downscale
self.mouse_captured = true
self.reticle = self.find_node("Reticle").as(Control)
self.stats = self.find_node("stats").as(Label)
self.stats.visible = config.show_stats
globals.capture_mouse = proc() =
self.mouse_captured = true
self.reticle = self.find_node("Reticle").as(Control)
self.stats = self.find_node("stats").as(Label)
self.stats.visible = config.show_stats
globals.capture_mouse = proc() =
self.mouse_captured = true
globals.release_mouse = proc() =
self.mouse_captured = false
globals.release_mouse = proc() =
self.mouse_captured = false
globals.reload_scripts = proc() =
trigger("save")
trigger("reload")
globals.reload_scripts = proc() =
trigger("save")
trigger("reload")
globals.save_and_reload = proc() =
trigger("save")
trigger("reload_all")
globals.save_scene()
globals.save_and_reload = proc() =
trigger("save")
trigger("reload_all")
globals.save_scene()
globals.save_scene = proc(immediate: bool) =
if immediate:
self.save_requested = some(now())
elif not self.save_requested:
self.save_requested = some(now() + 5.seconds)
globals.save_scene = proc(immediate: bool) =
if immediate:
self.save_requested = some(now())
elif not self.save_requested:
self.save_requested = some(now() + 5.seconds)
globals.pause = proc() =
trigger("pause")
self.ready = true
globals.pause = proc() =
trigger("pause")
self.ready = true
trigger("game_ready")
proc update_action_index*(change: int) =
action_index += change

View File

@ -1,4 +1,4 @@
import ../godotapi / [node, scene_tree, voxel_buffer],
import godotapi / [node, scene_tree, voxel_buffer],
godot, hashes,
engine/engine, core,
strformat, math, strutils, sequtils, compiler/lineinfos, tables, sets, json_serialization

View File

@ -1,4 +1,4 @@
import ../../godotapi / [sprite_3d, ray_cast, spatial]
import godotapi / [sprite_3d, ray_cast, spatial]
import godot, strutils, math
import ".." / [globals, core]

View File

@ -1,4 +1,4 @@
import ../../godotapi / [kinematic_body, spatial, input, input_event,
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]

View File

@ -1,4 +1,4 @@
import ../../godotapi / [area, control]
import godotapi / [area, control]
import godot
import ".." / [core, globals, game, world/bot]
import player

View File

@ -1,4 +1,4 @@
import ../../godotapi / [button, style_box_flat]
import godotapi / [button, style_box_flat]
import godot
import ".." / [core, globals]

View File

@ -1,4 +1,4 @@
import ../../godotapi / [text_edit, scene_tree, node, input_event, input_event_key,
import godotapi / [text_edit, scene_tree, node, input_event, input_event_key,
rich_text_label, global_constants]
import godot, strutils
import ".." / [globals, core]

View File

@ -1,4 +1,4 @@
import ../../godotapi / [text_edit, scene_tree, node, input_event, global_constants,
import godotapi / [text_edit, scene_tree, node, input_event, global_constants,
input_event_key, style_box_flat]
import godot, strutils, tables, compiler/lineinfos
import ".." / [core, globals, game, engine/engine]

View File

@ -1,4 +1,4 @@
import ../../godotapi / [viewport, camera, mesh_instance, material, camera,
import godotapi / [viewport, camera, mesh_instance, material, camera,
viewport_texture, image, resource_loader]
import godot
import ".." / [core, globals]

View File

@ -1,4 +1,4 @@
import ../../godotapi / [h_box_container, scene_tree, button, image_texture]
import godotapi / [h_box_container, scene_tree, button, image_texture]
import godot
import ".." / [core, globals, game, ui/preview_maker]

View File

@ -1,32 +1,32 @@
import ../../godotapi / [scene_tree, kinematic_body, material, mesh_instance, spatial,
input_event, animation_player, resource_loader, packed_scene]
import godotapi / [scene_tree, kinematic_body, material, mesh_instance, spatial,
input_event, animation_player, resource_loader, packed_scene]
import godot, std / [math, tables, with, times, sugar, os, monotimes]
import ".." / [globals, core]
import ../engine / [engine, script_helpers]
import globals, core
import engine/contexts
export contexts
include "default_robot.nim.nimf"
var max_bot_index = 0
gdobj NimBot of KinematicBody:
var
speed = 1.0
enu_script*: string
script_ctx*: ScriptCtx
material* {.gdExport.}, highlight_material* {.gdExport.},
selected_material* {.gdExport.}: Material
script_index* {.gdExport.} = 0
disabled* {.gdExport.} = false
callback: proc(delta: float): bool
saved_callback: proc(delta: float): bool
engine: Engine
last_error: string
orig_rotation: Vector3
orig_translation: Vector3
skin: Spatial
mesh: MeshInstance
paused* = true
running = false
animation_player: AnimationPlayer
advance_timer = MonoTime.high
proc init*() =
self.script_ctx = ScriptCtx.init("bot")
self.speed = 1.0
proc code_template(file: string, imports: string): string =
result = default_robot(config.script_dir & "/" & file, imports)
proc update_material*(value: Material) =
self.mesh.set_surface_material(0, value)
@ -41,154 +41,90 @@ gdobj NimBot of KinematicBody:
self.set_default_material()
proc select*() =
show_editor self.enu_script, self.engine
show_editor self.script_ctx.script, self.script_ctx.engine
proc destroy*() =
self.get_parent.remove_child(self)
proc print_error(msg: string) =
if msg != self.last_error:
self.last_error = msg
print(msg)
proc on_load_vars() =
let e = self.script_ctx.engine
self.speed = e.get_float("speed", e.module_name)
proc load_vars() =
self.speed = self.engine.get_float("speed", self.engine.module_name)
proc move(direction: Vector3, steps: float): bool =
self.load_vars()
proc on_begin_move(direction: Vector3, steps: float): Callback =
var duration = 0.0
let
moving = direction.rotated(UP, self.rotation.y)
finish = self.translation + moving * steps
finish_time = 1.0 / self.speed * steps
self.callback = proc(delta: float): bool =
duration += delta
if duration >= finish_time:
self.translation = finish
return false
else:
discard self.move_and_slide(moving * self.speed, UP)
return true
self.start_advance_timer()
true
when not defined(enu_simulate):
result = proc(delta: float): bool =
duration += delta
if duration >= finish_time:
self.translation = finish
return false
else:
discard self.move_and_slide(moving * self.speed, UP)
return true
current_ctx().start_advance_timer()
else:
self.translation = finish
proc turn(degrees: float): bool =
self.load_vars()
proc on_begin_turn(axis: Vector3, degrees: float): Callback =
assert axis in [LEFT, RIGHT]
let degrees = degrees * -axis.x
var duration = 0.0
# TODO: Why can't this be a one liner?
var final_transform = self.transform
final_transform.basis.rotate(UP, deg_to_rad(degrees))
self.callback = proc(delta: float): bool =
duration += delta
self.rotate(UP, deg_to_rad(degrees * delta * self.speed))
if duration <= 1.0 / self.speed:
true
else:
self.transform = final_transform
false
self.start_advance_timer()
true
proc forward(steps: float): bool = self.move(FORWARD, steps)
proc back(steps: float): bool = self.move(BACK, steps)
proc left(steps: float): bool = self.move(LEFT, steps)
proc right(steps: float): bool = self.move(RIGHT, steps)
proc turn_left(degrees: float): bool = self.turn(degrees)
proc turn_right(degrees: float): bool = self.turn(-degrees)
proc error(e: ref VMQuit) =
self.running = false
err e.msg
trigger("script_error")
proc is_script_loadable(): bool =
if self.enu_script != "none" and file_exists(self.enu_script):
let current_code = read_file(self.enu_script).strip
result = current_code != ""
proc load_script() =
trace:
self.callback = nil
try:
if self.engine.is_nil:
self.engine = Engine()
if not self.is_script_loadable:
return
if not self.paused and not self.engine.initialized:
debug &"Loading {self.enu_script}"
let code = default_robot(self.enu_script.extract_filename)
self.engine.load(config.script_dir, self.enu_script.split_file.name, code, config.lib_dir)
log_trace("loaded")
with self.engine:
expose "yield_script", proc(a: VmArgs): bool =
self.callback = self.saved_callback
self.saved_callback = nil
true
expose("forward_impl", a => self.forward(get_float(a, 0)))
expose("back_impl", a => self.back(get_float(a, 0)))
expose("left_impl", a => self.left(get_float(a, 0)))
expose("right_impl", a => self.right(get_float(a, 0)))
expose("turn_left_impl", a => self.turn_left(get_float(a, 0)))
expose("turn_right_impl", a => self.turn_right(get_float(a, 0)))
expose("echo", a => echo_console(get_string(a, 0)))
expose("play", proc(a: VmArgs): bool =
let animation_name = get_string(a, 0)
if animation_name == "":
self.animation_player.stop(true)
else:
self.animation_player.play(animation_name)
return false
)
log_trace("exposed")
if not self.paused:
self.running = self.engine.run()
log_trace("running")
except VMQuit as e:
self.error(e)
proc set_script() =
self.enu_script = join_path(config.script_dir, &"bot_{self.script_index}.nim")
when not defined(enu_simulate):
result = proc(delta: float): bool =
duration += delta
self.rotate(UP, deg_to_rad(degrees * delta * self.speed))
if duration <= 1.0 / self.speed:
true
else:
self.transform = final_transform
false
current_ctx().start_advance_timer()
else:
self.transform = final_transform
proc setup*() =
trace:
self.script_index = max_bot_index
inc max_bot_index
self.name = "Bot_" & $self.script_index
self.set_script()
write_file self.enu_script, ""
self.script_index = max_bot_index
inc max_bot_index
self.name = "Bot_" & $self.script_index
self.set_script()
write_file self.script_ctx.script, ""
method ready*() =
trace:
if max_bot_index <= self.script_index:
max_bot_index = self.script_index + 1
self.set_script()
with self:
skin = self.get_node("Mannequiny").as(Spatial)
mesh = self.skin.get_node("root/Skeleton/body001").as(MeshInstance)
animation_player = self.skin.get_node("AnimationPlayer").as(AnimationPlayer)
orig_rotation = self.rotation
orig_translation = self.translation
set_default_material()
self.bind_signals("game_ready")
if max_bot_index <= self.script_index:
max_bot_index = self.script_index + 1
self.set_script()
with self:
skin = self.get_node("Mannequiny").as(Spatial)
mesh = self.skin.get_node("root/Skeleton/body001").as(MeshInstance)
animation_player = self.skin.get_node("AnimationPlayer").as(AnimationPlayer)
orig_rotation = self.rotation
orig_translation = self.translation
set_default_material()
if not self.disabled:
self.bind_signals(w"reload pause reload_all")
log_trace()
self.load_script()
log_trace("load_script")
method on_game_ready() =
if not self.disabled:
self.bind_signals(w"reload pause reload_all")
self.load_script()
proc update_running_state(running: bool) =
self.running = running
self.engine.running = running
method physics_process*(delta: float64) =
trace:
if not self.paused:
try:
if self.running:
self.advance(delta)
except VMQuit as e:
self.error(e)
if not self.paused:
try:
if self.engine.running:
self.advance(delta)
except VMQuit as e:
self.engine.error(e)
method reload() =
self.animation_player.stop(true)
@ -197,8 +133,17 @@ gdobj NimBot of KinematicBody:
rotation = self.orig_rotation
self.load_script()
proc on_script_loaded*(e: Engine) =
e.expose "play", proc(a: VmArgs): bool =
let animation_name = get_string(a, 0)
if animation_name == "":
self.animation_player.stop(true)
else:
self.animation_player.play(animation_name)
return false
method on_reload*() =
if not editing() or open_file == self.enu_script:
if not editing() or open_file == self.script:
self.paused = false
self.reload()

View File

@ -1,8 +1,9 @@
import ../../godotapi / [spatial, grid_map]
import godotapi / [spatial, grid_map]
import godot
import std / [tables, math, sets, sugar, sequtils, hashes, os, monotimes]
import ".." / [core, globals, world/terrain, world/grid]
import ../engine / [engine, script_helpers]
import core, globals, world / [terrain, grid]
import engine/contexts
export contexts
include "default_builder.nim.nimf"
@ -13,9 +14,9 @@ type
gdobj Builder of Spatial:
var
script_ctx: ScriptCtx
draw_mode* {.gdExport.}: DrawMode
script_index* {.gdExport.} = 0
enu_script*: string
initial_index* {.gdExport.} = 1
# FIXME `saved_blocks` and `saved_block_colors` should be
# a single `seq[(Vector, int)]`. Will need `to_variant`
@ -24,10 +25,6 @@ gdobj Builder of Spatial:
saved_block_colors {.gdExport.}: seq[int]
saved_holes* {.gdExport}: seq[Vector3]
original_translation* {.gdExport.}: Vector3
paused* = false
engine: Engine
callback: proc(delta: float): bool
saved_callback: proc(delta: float): bool
index: int = 1
blocks_per_frame = 0.0
blocks_remaining_this_frame = 0.0
@ -42,34 +39,35 @@ gdobj Builder of Spatial:
built = false
move_mode = false
scale_factor = 1.0
running = false
# If we delete a voxel while it's queued up to be drawn by godot voxel,
# the voxel gets drawn anyway, and the polys hang around until they
# move out of draw distance. Wait a bit before clearing. FIXME.
timers: seq[Timer]
advance_timer = MonoTime.high
proc init*() =
self.script_ctx = ScriptCtx.init("grid")
proc code_template(file: string, imports: string): string =
result = default_builder(file, imports)
echo "template: ", result
method ready() =
trace:
if max_grid_index <= self.script_index:
max_grid_index = self.script_index + 1
self.set_script()
self.translation = self.original_translation
self.grid = self.get_node("Grid") as Grid
self.terrain = game_node.find_node("Terrain") as Terrain
assert self.grid != nil
assert self.terrain != nil
if max_grid_index <= self.script_index:
max_grid_index = self.script_index + 1
self.set_script()
self.translation = self.original_translation
self.grid = self.get_node("Grid") as Grid
self.terrain = game_node.find_node("Terrain") as Terrain
assert self.grid != nil
assert self.terrain != nil
self.bind_signals self.terrain,
w"block_selected last_block_deleted terrain_block_added terrain_block_removed"
self.bind_signals self.grid, w"selected deleted grid_block_added grid_block_removed"
self.bind_signals w"reload pause reload_all"
self.bind_signals self.terrain,
w"block_selected last_block_deleted terrain_block_added terrain_block_removed"
self.bind_signals self.grid, w"selected deleted grid_block_added grid_block_removed"
self.bind_signals w"reload pause reload_all"
if self.saved_blocks.len > 0 or self.saved_holes.len > 0:
self.restore_blocks()
proc set_script() =
self.enu_script = join_path(config.script_dir, &"grid_{self.script_index}.nim")
if self.saved_blocks.len > 0 or self.saved_holes.len > 0:
self.restore_blocks()
proc setup*(translation: Vector3) =
self.translation = translation
@ -78,11 +76,7 @@ gdobj Builder of Spatial:
inc max_grid_index
self.name = "Builder_" & $self.script_index
self.set_script()
write_file self.enu_script, ""
proc is_script_loadable(): bool =
if self.enu_script != "none" and file_exists(self.enu_script):
result = read_file(self.enu_script).strip != ""
write_file self.script, ""
proc save_blocks*() =
let data = if self.draw_mode == VoxelMode:
@ -118,13 +112,13 @@ gdobj Builder of Spatial:
self.grid.draw(x, y, z, index, keep, trigger)
proc update_running_state(running: bool) =
self.running = running
if not self.running:
self.engine.running = running
if not running:
self.holes = self.kept_holes
self.kept_holes.clear()
self.save_blocks()
self.load_vars()
debug(self.enu_script & " done.")
debug(self.script & " done.")
proc includes_any_location*(locations: seq[Vector3]): bool =
if self.draw_mode == GridMode:
@ -147,7 +141,7 @@ gdobj Builder of Spatial:
if self.draw_mode == VoxelMode:
self.position.origin = self.translation
self.speed = 30.0
self.speed = 1.0
self.scale_factor = 1.0
self.grid.scale = vec3(1, 1, 1)
self.index = 1
@ -186,7 +180,7 @@ gdobj Builder of Spatial:
self.draw_mode = mode
self.save_blocks()
proc load_vars() =
proc on_load_vars() =
var old_speed = self.speed
let
e = self.engine
@ -200,7 +194,7 @@ gdobj Builder of Spatial:
self.blocks_per_frame = if self.speed == 0:
float.high
else:
self.speed / 30.0
self.speed
if self.speed != old_speed:
self.blocks_remaining_this_frame = 0
if scale_factor != self.scale_factor:
@ -211,36 +205,25 @@ gdobj Builder of Spatial:
self.set_vars()
method physics_process(delta: float64) =
trace:
if self.timers.len > 0:
var timers = self.timers
self.timers = @[]
for timer in timers:
if timer.until < now():
timer.callback()
else:
self.timers.add timer
return
if self.timers.len > 0:
var timers = self.timers
self.timers = @[]
for timer in timers:
if timer.until < now():
timer.callback()
else:
self.timers.add timer
return
if not self.built:
self.build()
self.built = true
if not self.paused:
self.blocks_remaining_this_frame += self.blocks_per_frame
try:
if self.move_mode:
if self.running:
self.advance(delta)
else:
if self.blocks_per_frame > 0:
while self.running and self.blocks_remaining_this_frame >= 1:
self.advance(delta)
else:
if self.running:
self.advance(delta)
except VMQuit as e:
self.error(e)
if not self.built:
self.build()
self.built = true
if not self.paused:
try:
if self.engine.running:
self.advance(delta)
except VMQuit as e:
self.engine.error(e)
proc set_vars() =
let module_name = self.engine.module_name
@ -248,8 +231,7 @@ gdobj Builder of Spatial:
self.speed, self.scale_factor, int self.draw_mode,
self.overwrite, self.move_mode)
proc move(direction: Vector3, steps: float): bool =
self.load_vars()
proc on_begin_move(direction: Vector3, steps: float): Callback =
if self.move_mode:
let steps = steps.float
var duration = 0.0
@ -258,7 +240,7 @@ gdobj Builder of Spatial:
finish = self.translation + moving * steps
finish_time = 1.0 / self.speed * steps
self.callback = proc(delta: float): bool =
result = proc(delta: float): bool =
duration += delta
if duration >= finish_time:
self.translation = finish
@ -266,29 +248,32 @@ gdobj Builder of Spatial:
else:
self.translation = self.translation + (moving * self.speed * delta)
return true
self.start_advance_timer()
return true
else:
var count = 0
self.callback = proc(delta: float): bool =
result = proc(delta: float): bool =
self.blocks_remaining_this_frame += self.blocks_per_frame
var remaining = self.blocks_remaining_this_frame
var per_frame = self.blocks_per_frame
while count.float < steps and self.blocks_remaining_this_frame >= 1:
remaining = self.blocks_remaining_this_frame
per_frame = self.blocks_per_frame
self.position = self.position.translated(direction)
inc count
self.blocks_remaining_this_frame -= 1
self.drop_block()
return count.float < steps
self.start_advance_timer()
return true
current_ctx().start_advance_timer()
proc turn(degrees: float, axis = UP): bool =
self.load_vars()
proc on_begin_turn(axis = UP, degrees: float): Callback =
let map = {LEFT: UP, RIGHT: DOWN, UP: RIGHT, DOWN: LEFT}.to_table
let axis = map[axis]
if self.move_mode:
var duration = 0.0
let axis = self.transform.basis.xform(axis)
var final_transform = self.transform
final_transform.basis = final_transform.basis.rotated(axis, deg_to_rad(degrees))
.orthonormalized()
self.callback = proc(delta: float): bool =
result = proc(delta: float): bool =
duration += delta
self.rotate(axis, deg_to_rad(degrees * delta * self.speed))
if duration <= 1.0 / self.speed:
@ -296,23 +281,15 @@ gdobj Builder of Spatial:
else:
self.transform = final_transform
false
self.start_advance_timer()
return true
current_ctx().start_advance_timer()
else:
let axis = self.position.basis.xform(axis)
self.position.basis = self.position.basis.rotated(axis, deg_to_rad(degrees))
self.position = self.position.orthonormalized()
return false
proc sleep(seconds: float): bool =
var duration = 0.0
proc on_sleep(seconds: float) =
self.blocks_per_frame = 0.0
self.blocks_remaining_this_frame = 0.0
self.callback = proc(delta: float): bool =
duration += delta
return duration < seconds
self.start_advance_timer()
true
proc save(name: string): bool =
self.load_vars()
@ -324,21 +301,28 @@ gdobj Builder of Spatial:
if self.engine.initialized: self.set_vars()
false
proc forward(steps: float): bool = self.move(FORWARD, steps)
proc back(steps: float): bool = self.move(BACK, steps)
proc up(steps: float): bool = self.move(UP, steps)
proc down(steps: float): bool = self.move(DOWN, steps)
proc left(steps: float): bool = self.move(LEFT, steps)
proc right(steps: float): bool = self.move(RIGHT, steps)
proc turn_left(degrees: float): bool = self.turn(degrees, UP)
proc turn_right(degrees: float): bool = self.turn(degrees, DOWN)
proc turn_up(degrees: float): bool = self.turn(degrees, RIGHT)
proc turn_down(degrees: float): bool = self.turn(degrees, LEFT)
proc error(e: ref VMQuit) =
self.running = false
err e.msg
trigger("script_error")
proc on_script_loaded(e: Engine) =
self.blocks_remaining_this_frame = 0
e.expose "set_energy", proc(a: VmArgs): bool =
let
color = get_int(a, 0).int
energy = get_float(a, 1)
if self.draw_mode == GridMode:
self.grid.set_energy(color, energy)
else:
self.terrain.set_energy(color, energy, self.script_index)
false
e.expose("save", a => self.save(get_string(a, 0)))
e.expose("restore", a => self.restore(get_string(a, 0)))
e.expose "reset", proc(a: VmArgs): bool =
self.reset(get_bool(a, 0))
false
e.expose "pause", proc(a: VmArgs): bool =
self.paused = true
true
e.expose "load_defaults", proc(a: VmArgs): bool =
self.set_vars()
false
proc clear() =
if self.draw_mode == VoxelMode:
@ -372,77 +356,25 @@ gdobj Builder of Spatial:
self.draw(p.x, p.y, p.z, action_index)
self.load_script()
proc load_script() =
self.callback = nil
self.blocks_remaining_this_frame = 0
try:
if self.engine.is_nil: self.engine = Engine()
if not self.is_script_loadable:
return
if not (self.paused or self.engine.initialized):
let code = default_builder(self.enu_script.extract_filename)
with self.engine:
load(config.script_dir, self.enu_script.split_file.name, code, config.lib_dir)
expose "yield_script", proc(a: VmArgs):bool =
self.callback = self.saved_callback
self.saved_callback = nil
true
expose("up_impl", a => self.up(get_float(a, 0)))
expose("down_impl", a => self.down(get_float(a, 0)))
expose("left_impl", a => self.left(get_float(a, 0)))
expose("right_impl", a => self.right(get_float(a, 0)))
expose("forward_impl", a => self.forward(get_float(a, 0)))
expose("back_impl", a => self.back(get_float(a, 0)))
expose("turn_left_impl", a => self.turn_left(get_float(a, 0)))
expose("turn_right_impl", a => self.turn_right(get_float(a, 0)))
expose("turn_up_impl", a => self.turn_up(get_float(a, 0)))
expose("turn_down_impl", a => self.turn_down(get_float(a, 0)))
expose("echo_console", a => echo_console(get_string(a, 0)))
expose("sleep_impl", a => self.sleep(get_float(a, 0)))
expose("save", a => self.save(get_string(a, 0)))
expose("restore", a => self.restore(get_string(a, 0)))
expose "set_energy", proc(a: VmArgs): bool =
let
color = get_int(a, 0).int
energy = get_float(a, 1)
if self.draw_mode == GridMode:
self.grid.set_energy(color, energy)
else:
self.terrain.set_energy(color, energy, self.script_index)
false
expose "reset", proc(a: VmArgs): bool =
self.reset(get_bool(a, 0))
false
expose "pause", proc(a: VmArgs): bool =
self.paused = true
true
expose "load_defaults", proc(a: VmArgs): bool =
self.set_vars()
false
if not self.paused:
self.update_running_state self.engine.run()
except VMQuit as e:
self.error(e)
method on_block_selected(offset: int) =
if offset == self.script_index:
show_editor self.enu_script, self.engine
show_editor self.script, self.engine
method on_selected() =
show_editor self.enu_script, self.engine
show_editor self.script, self.engine
proc set_timer(duration: TimeInterval, callback: proc()) =
self.timers.add (now() + duration, callback)
method reload() =
let duration = if self.running: 0.5.seconds else: 0.seconds
let duration = if self.engine.running: 0.5.seconds else: 0.seconds
self.set_timer duration, proc() =
self.reset(clear = true, set_vars = false)
self.paused = false
self.load_script()
method on_reload() =
if not editing() or open_file == self.enu_script:
if not editing() or open_file == self.script:
self.reload()
method on_reload_all() =

View File

@ -1,6 +1,10 @@
#? stdtmpl
#proc default_builder(file_name: string): string =
proc quit*(exit_code = 0) = discard
#proc default_builder(file_name, imports: string): string =
import types
${imports}
template name(n: untyped) = class_name(n, ScriptNode3D)
preprocess "name", "${file_name}", "ScriptNode3D"
include builder
include "${file_name}"

View File

@ -1,6 +1,10 @@
#? stdtmpl
#proc default_robot(file_name: string): string =
proc quit*(exit_code = 0) = discard
#proc default_robot(file_name, imports: string): string =
import types
${imports}
template name(n: untyped) = class_name(n, ScriptNode)
preprocess "name", "${file_name}", "ScriptNode"
include robot
include "${file_name}"

View File

@ -1,4 +1,4 @@
import ../../godotapi/[grid_map, mesh_library, mesh, spatial, spatial_material]
import godotapi/[grid_map, mesh_library, mesh, spatial, spatial_material]
import godot, sets
import ".." / [core, globals, world/builder]

View File

@ -1,7 +1,6 @@
import ../../godotapi / [mesh_instance, node, spatial, resource_loader, packed_scene]
import godotapi / [mesh_instance, node, spatial, resource_loader, packed_scene]
import godot, sugar
import ".." / [core, globals]
import ../world / [builder, bot, terrain]
import core, globals, world / [builder, bot, terrain]
gdobj Ground of MeshInstance:
var

View File

@ -1,4 +1,4 @@
import ../../godotapi / [mesh, voxel_terrain, voxel_tool, voxel, voxel_library, shader_material]
import godotapi / [mesh, voxel_terrain, voxel_tool, voxel, voxel_library, shader_material]
import godot, sets, tables, hashes
import ".." / [globals, core]

36
vmlib/enu/base_api.nim Normal file
View File

@ -0,0 +1,36 @@
import random
# API
proc quit*(exit_code = 0) = discard
proc echo_console(msg: string) = discard
proc echo(msg: varargs[string, `$`]) = echo_console msg.join
proc begin_move*(direction: Vector3, steps: float) = discard
proc begin_turn*(axis: Vector3, steps: float) = discard
proc look_at_impl(node: Node) = discard
proc near(node: Node, distance = 5):bool = discard
proc forward*(steps = 1.0) = self.wait begin_move(FORWARD, steps)
proc back*(steps = 1.0) = self.wait begin_move(BACK, steps)
proc left*(steps = 1.0) = self.wait begin_move(LEFT, steps)
proc right*(steps = 1.0) = self.wait begin_move(RIGHT, steps)
proc turn_left*(degrees = 90.0) = self.wait begin_turn(LEFT, degrees)
proc turn_right*(degrees = 90.0) = self.wait begin_turn(RIGHT, degrees)
proc look_at*(node: Node) = self.wait look_at_impl(node)
proc fd*(steps = 1.0) = forward(steps)
proc bk*(steps = 1.0) = back(steps)
proc lt*(steps = 1.0) = left(steps)
proc rt*(steps = 1.0) = right(steps)
proc tl*(degrees = 90.0) = turn_left(degrees)
proc tr*(degrees = 90.0) = turn_right(degrees)
when not declared(skip_3d):
proc up*(steps = 1.0) = self.wait begin_move(UP, steps)
proc down*(steps = 1.0) = self.wait begin_move(DOWN, steps)
proc turn_up*(degrees = 0.0) = self.wait begin_turn(UP, degrees)
proc turn_down*(degrees = 90.0) = self.wait begin_turn(DOWN, degrees)
proc tu*(degrees = 90.0) = turn_up(degrees)
proc td*(degrees = 90.0) = turn_down(degrees)

View File

@ -1,24 +1,10 @@
import strformat, strutils, helpers
import strformat, strutils, helpers, types
export helpers
include loops
type
ColorIndex* = enum
eraser = 0,
blue = 1,
red = 2,
green = 3,
black = 4,
white = 5
DrawMode* = enum
GridMode, VoxelMode
Energy = range[0.0..100.0]
include loops, core
var
speed*: range[0.0..250.0] = 30.0
speed*: range[0.0..250.0] = 1.0
move_speed = 1.0
drawing* = true
color*: ColorIndex
@ -28,6 +14,21 @@ var
scale* = 1.0
energies: Table[ColorIndex, float]
include base_api
self.ctrl.begin_move = proc(direction: Vector3, steps: float, self: ScriptNode) =
self.wait begin_move(direction, steps)
self.ctrl.begin_turn = proc(axis: Vector3, degrees: float, self: ScriptNode) =
self.wait begin_turn(axis, degrees)
self.ctrl.advance_state_machine = proc(): bool = advance_state_machine()
self.ctrl.yield_script = proc() = yield_script()
self.ctrl.set = proc(name: string, new_speed:float) =
speed = new_speed
self.ctrl.get = proc(name: string): float = speed
proc change_color(amount: int) =
var color_index = int color
color_index += amount
@ -37,9 +38,6 @@ proc change_color(amount: int) =
color_index = int ColorIndex.high
color = ColorIndex color_index
include logo
proc echo_console*(msg: string) = discard
proc sleep*(seconds: float) = discard
proc reset*(clear = false) = discard
proc save*(name = "default") = discard

13
vmlib/enu/core.nim Normal file
View File

@ -0,0 +1,13 @@
type
ColorIndex* = enum
eraser = 0,
blue = 1,
red = 2,
green = 3,
black = 4,
white = 5
DrawMode* = enum
GridMode, VoxelMode
Energy* = range[0.0..100.0]

View File

@ -1,39 +0,0 @@
import random, types
# API
proc forward_impl*(steps: float) = discard
proc back_impl(steps: float) = discard
proc left_impl(steps: float) = discard
proc right_impl(steps: float) = discard
proc turn_left_impl(degrees: float) = discard
proc turn_right_impl(degrees: float) = discard
proc look_at_impl(node: Node) = discard
proc near*(node: Node, distance = 5):bool = discard
proc forward*(steps = 1.0) = wait forward_impl(steps)
proc back*(steps = 1.0) = wait back_impl(steps)
proc left*(steps = 1.0) = wait left_impl(steps)
proc right*(steps = 1.0) = wait right_impl(steps)
proc turn_left*(degrees = 90.0) = wait turn_left_impl(degrees)
proc turn_right*(degrees = 90.0) = wait turn_right_impl(degrees)
proc look_at*(node: Node) = wait look_at_impl(node)
proc fd*(steps = 1.0) = forward(steps)
proc bk*(steps = 1.0) = back(steps)
proc lt*(steps = 1.0) = left(steps)
proc rt*(steps = 1.0) = right(steps)
proc tl*(degrees = 90.0) = turn_left(degrees)
proc tr*(degrees = 90.0) = turn_right(degrees)
when not declared(skip_3d):
proc up_impl(steps: float) = discard
proc down_impl(steps: float) = discard
proc turn_up_impl(degrees: float) = discard
proc turn_down_impl(degrees: float) = discard
proc up*(steps = 1.0) = wait up_impl(steps)
proc down*(steps = 1.0) = wait down_impl(steps)
proc turn_up*(degrees = 0.0) = wait turn_up_impl(degrees)
proc turn_down*(degrees = 90.0) = wait turn_down_impl(degrees)
proc tu*(degrees = 90.0) = turn_up(degrees)
proc td*(degrees = 90.0) = turn_down(degrees)

View File

@ -1,24 +1,25 @@
import state_machine
import state_machine, types
var
context: Context
action_running = true
proc yield_script = discard
proc advance_state_machine(): bool =
if not context.is_nil:
result = context.advance()
result = if not context.is_nil:
context.advance()
else:
true
proc set_action_running*(running: bool) =
action_running = running
self.ctrl.action_running = running
template wait(body: untyped) =
action_running = true
template wait(node: ScriptNode, body: untyped) =
node.ctrl.action_running = true
when defined(nimscript):
body
while action_running and advance_state_machine():
yield_script()
while node.ctrl.action_running and node.ctrl.advance_state_machine():
node.ctrl.yield_script()
else:
# only for tests
var counter = 0

View File

@ -1,16 +1,29 @@
import helpers, strutils
import helpers, strutils, types
export helpers
include loops
let skip_3d = true
include logo
var
speed* = 1.0
player* = Node()
#proc echo*(msg: string) = discard
include base_api
self.ctrl.begin_move = proc(direction: Vector3, steps: float, self: ScriptNode) =
self.wait begin_move(direction, steps)
self.ctrl.begin_turn = proc(axis: Vector3, degrees: float, self: ScriptNode) =
self.wait begin_turn(axis, degrees)
self.ctrl.advance_state_machine = proc(): bool = advance_state_machine()
self.ctrl.yield_script = proc() = yield_script()
self.ctrl.set = proc(name: string, new_speed:float) =
speed = new_speed
self.ctrl.get = proc(name: string): float = speed
proc play*(animation_name: string) = discard
proc set_speed*(spd: float) = speed = spd

View File

@ -1,3 +1,93 @@
import helpers, strutils, strformat
import macros
type
Node* = ref object
Vector3* = object
x*, y*, z*: float
Node* = ref object of RootObj
id: int
name*: string
Controller* = object of RootObj
action_running*: bool
advance_state_machine*: proc(): bool
yield_script*: proc()
begin_move*: proc(direction: Vector3, steps: float, self: ScriptNode)
begin_turn*: proc(axis: Vector3, degrees: float, self: ScriptNode)
set*: proc(var_name: string, value: float)
get*: proc(var_name: string): float
ScriptNode* = ref object of Node
ctrl*: Controller
ScriptNode3D* = ref object of ScriptNode
proc vec3*(x, y, z: float): Vector3 {.inline.} =
Vector3(x:x, y:y, z:z)
const
UP* = vec3(0, 1, 0)
DOWN* = vec3(0, -1, 0)
BACK* = vec3(0, 0, 1)
FORWARD* = vec3(0, 0, -1)
RIGHT* = vec3(1, 0, 0)
LEFT* = vec3(-1, 0, 0)
macro preprocess*(ident_name, file, class_name: static string): untyped =
let ast = parse_stmt file.static_read
# only checking top level for now. Make this more robust.
for node in ast:
if node.kind in [nnkCommand, nnkCall]:
if node.len == 2 and node[1].kind == nnkIdent and node[0].eq_ident(ident_name):
return node
return parse_stmt(&"let self = {class_name}(ctrl: Controller())")
macro class_name*(name, base_class: untyped): untyped =
name.expect_kind nnkIdent
let name_str = name.str_val
let type_name = (name_str & "Type").to_upper_ascii.nim_ident_normalize.ident
let var_name = name_str.ident
result = quote do:
when not declared(self) and not declared(`var_name`) and not declared(`type_name`):
type
`type_name`* = ref object of `base_class`
let `var_name`* {.inject.} = `type_name`(name: `name_str`, ctrl: Controller())
let self {.inject.} = `var_name`
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")
template up*(target: ScriptNode3D, steps = 1.0) =
target.ctrl.begin_move(UP, steps, self)
template down*(target: ScriptNode3D, steps = 1.0) =
target.ctrl.begin_move(DOWN, steps, self)
template turn_up*(target: ScriptNode3D, degrees = 90.0) =
target.ctrl.begin_turn(UP, degrees, self)
template turn_down*(target: ScriptNode3D, degrees = 90.0) =
target.ctrl.begin_turn(DOWN, degrees, self)