diff --git a/.gitignore b/.gitignore index 6405e25..d3eacb0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ build NVL/.vs NVL/out .cache +vs +**/.DS_STORE diff --git a/ADVect/ADVect.cpp b/ADVect/ADVect.cpp index e5199fa..2d60b5d 100644 --- a/ADVect/ADVect.cpp +++ b/ADVect/ADVect.cpp @@ -35,7 +35,7 @@ namespace { ADVect::MarkupTextTransitionTrack m_text{ .current{}, - .pos_x = 280, .pos_y = 90, .w = 900, .h = 500, + .pos_x = 280, .pos_y = 90, .w = 800, .h = 500, .opacity = 1.0f, .fill = 0xFFFFFFFF }; ADVect::TextTrack speaker = { @@ -50,7 +50,7 @@ namespace { .w = 500, .h = 500 }, avatar = { .current{}, - .pos_x = 10, .pos_y = 0, + .pos_x = 0, .pos_y = 0, .w = 500, .h = 500 }; ADVect::ImageTrack dialogue_bg = { @@ -58,28 +58,7 @@ namespace { .pos_x = 0, .pos_y = 0, .w = 500, .h = 500 }; - /* - std::list tracks_img{}; // instead of vector because we need pointers to members; prevent use after free - NamedImageTrack* find_image_track(const std::string& s) { - for (auto& t : tracks_img) - if (t.name == s) { - return &t; - } - - return nullptr; - } - bool add_image_track(const std::string& s) { - if (find_image_track(s) == nullptr) { - NamedImageTrack t; - t.name = s; - tracks_img.push_back(std::move(t)); - return true; - } - std::cerr << "ADV: Redefinition of ImageTrack " << s << std::endl; - return false; - } - */ - + std::vector> track_updaters; } namespace ADVect { @@ -140,6 +119,10 @@ namespace ADVect { return false; } + track_updaters.push_back(std::bind(&decltype(bg)::check, &bg, std::placeholders::_1)); + track_updaters.push_back(std::bind(&decltype(avatar)::check, &avatar, std::placeholders::_1)); + track_updaters.push_back(std::bind(&decltype(m_text)::check, &m_text, std::placeholders::_1)); + Advance(); return true; } @@ -195,20 +178,15 @@ namespace ADVect { Advance(); } - bg.check(current_time); - avatar.check(current_time); - m_text.check(current_time); + for (const auto& x : track_updaters) { + x(current_time); + } + } void Render() { bgfx::touch(0); - /* - for (auto& t : tracks_img) { - if (t.current != nullptr) - ADVect::Graphics::DrawTextureImage(*t.current, t.pos_x, t.pos_y); - } - */ draw_image_transition_fade(current_time, bg); if (draw_ui) { @@ -216,7 +194,7 @@ namespace ADVect { ADVect::Graphics::DrawTextureImage(*dialogue_bg.current, dialogue_bg.pos_x, dialogue_bg.pos_y); draw_image_transition_crossfade(current_time, avatar); - ADVect::Graphics::RenderString(speaker.current, speaker.pos_x, speaker.pos_y, speaker.fill, ADVect::Graphics::TEXT_BOLD); + ADVect::Graphics::RenderString(speaker.current, speaker.pos_x, speaker.pos_y, speaker.fill, ADVect::Graphics::TEXT_BOLD); draw_typewriter(current_time, m_text); } @@ -276,47 +254,44 @@ int main(int argc, char* argv[]) { { u"Say", NVL::Environment::Variable([](std::vector args) { - NVL::Environment::MarkupString str = NVL::Environment::UnpackMarkupVariable(std::get>(NVL::Environment::Variable(args[0]).value)); + NVL::Environment::MarkupString str = NVL::Environment::UnpackMarkupVariable(std::get>(args[0].value)); size_t len = str.length(); m_text.change(current_time, str, len * 10, [len](NVL::Number x) { return static_cast(x * len); }); return NVL::Environment::Type::Nil; - }, 2) + }, 1) }, { u"SwitchSpeaker", NVL::Environment::Variable([](std::vector args) { - speaker.current = std::get(NVL::Environment::Variable(args[0]).value); + speaker.current = std::get(args[0].value); return NVL::Environment::Type::Nil; }, 1) }, -/* { - u"ImageTrack", + { + u"BG", NVL::Environment::Variable([](std::vector args) { - add_image_track(NVL::to_std_string(std::get(NVL::Environment::Variable(args[0]).value))); + bg.change(current_time, ADVect::Graphics::GetImageTextureFromFile(NVL::to_std_string(std::get(args[0].value))), 200); return NVL::Environment::Type::Nil; }, 1) - },*/ + }, + { + u"Avatar", + NVL::Environment::Variable([](std::vector args) { + avatar.change(current_time, ADVect::Graphics::GetImageTextureFromFile(NVL::to_std_string(std::get(args[0].value))), 200); + return NVL::Environment::Type::Nil; + }, 1) + }, + { + u"BGDialogue", + NVL::Environment::Variable([](std::vector args) { + dialogue_bg.current = ADVect::Graphics::GetImageTextureFromFile(NVL::to_std_string(std::get(args[0].value))); + return NVL::Environment::Type::Nil; + }, 1) + }, { u"Show", NVL::Environment::Variable([](std::vector args) { - std::string name = NVL::to_std_string(std::get(NVL::Environment::Variable(args[0]).value)); -/* NamedImageTrack* t = find_image_track(name); - if (t == nullptr) { - std::cerr << "ADV: Cannot find ImageTrack " << name << std::endl; - } - else { - auto s = std::get(NVL::Environment::Variable(args[1]).value); - auto s2 = NVL::to_std_string(s); - t->current = ADVect::Graphics::GetImageTextureFromFile(s2); - }*/ - if (name == "BG") - bg.change(current_time, ADVect::Graphics::GetImageTextureFromFile(NVL::to_std_string(std::get(NVL::Environment::Variable(args[1]).value))), 200); - else if (name == "Avatar") - avatar.change(current_time, ADVect::Graphics::GetImageTextureFromFile(NVL::to_std_string(std::get(NVL::Environment::Variable(args[1]).value))), 200); - else if (name == "BGDialogue") - dialogue_bg.current = ADVect::Graphics::GetImageTextureFromFile(NVL::to_std_string(std::get(NVL::Environment::Variable(args[1]).value))); - else - std::cerr << "ADV: Cannot find ImageTrack " << name << std::endl; + std::get)>>(args[0].value)({args[1]}); return NVL::Environment::Type::Nil; }, 2) } diff --git a/ADVect/CMakeLists.txt b/ADVect/CMakeLists.txt index 02653b9..32d9ffc 100644 --- a/ADVect/CMakeLists.txt +++ b/ADVect/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required (VERSION 3.8) project (ADVect) -add_executable (Game "ADVect.cpp" "Graphics.cpp" "Track.h") +add_executable (Game "ADVect.cpp" "Graphics.cpp" "Track.h" "Audio.cpp" "Audio.h") # TODO: Add tests and install targets if needed. include_directories ("include" "../NVL/") diff --git a/ADVect/Graphics.cpp b/ADVect/Graphics.cpp index 833a512..0068b43 100644 --- a/ADVect/Graphics.cpp +++ b/ADVect/Graphics.cpp @@ -1,5 +1,4 @@ #include "Graphics.h" -#include "Environment.h" #include #include FT_FREETYPE_H @@ -7,6 +6,7 @@ #include #include +#include #define STB_IMAGE_IMPLEMENTATION #include @@ -73,29 +73,48 @@ namespace { struct Font { FT_Face face = nullptr; - std::unordered_map cache{}; + std::unordered_map, u32, u32, i32, i32, i32, i32>> cache{}; - void cache_char(NVL::Char c) { - FT_GlyphSlot slot = face->glyph; + std::tuple, u32, u32, i32, i32, i32, i32>& get_char(NVL::Char c) { + if (cache.find(c) == cache.end()) { + FT_GlyphSlot slot = face->glyph; - error = FT_Load_Char(face, c, FT_LOAD_RENDER); - if (error) { - std::cerr << "ADV: Failed to load character" << std::endl; - } - - if (cache.find(c) == cache.end() && slot->bitmap.width != 0) { - const bgfx::Memory* buf = bgfx::copy(slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows); - - cache.emplace(c, bgfx::createTexture2D( - slot->bitmap.width, - slot->bitmap.rows, - false, - 1, - bgfx::TextureFormat::A8, - BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP, - buf - )); + error = FT_Load_Char(face, c, FT_LOAD_RENDER); + if (error) { + std::cerr << "ADV: Failed to load character" << std::endl; + } + + if (slot->bitmap.width != 0) { + const bgfx::Memory* buf = bgfx::copy(slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows); + + cache.emplace(c, std::make_tuple( + bgfx::createTexture2D( + slot->bitmap.width, + slot->bitmap.rows, + false, + 1, + bgfx::TextureFormat::A8, + BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP, + buf + ), + slot->bitmap.width, + slot->bitmap.rows, + slot->bitmap_left, + slot->bitmap_top, + slot->advance.x, + slot->advance.y + )); + } + else cache.emplace(c, std::make_tuple( + std::nullopt, + 0, + 0, + 0, + 0, + slot->advance.x, + slot->advance.y)); } + return cache[c]; } }; @@ -124,7 +143,8 @@ namespace { void destroy_font(Font& f) { FT_Done_Face(f.face); for (auto it = f.cache.begin(); it != f.cache.end(); it++) { - bgfx::destroy(it->second); + if(auto& tx = std::get<0>(it->second)) + bgfx::destroy(*tx); } } @@ -179,7 +199,20 @@ namespace { } return f; } - + + inline void HandleMarkupFlags(const NVL::Environment::MarkupSegment& s, u32& style_flags, const NVL::String*& ruby) { + for (const auto& e : s.efs) { + if (e.first == u"b") { style_flags |= TEXT_BOLD; } + else if (e.first == u"i") { style_flags |= TEXT_ITALIC; } + else if (e.first == u"u") { style_flags |= TEXT_UNDERLINE; } + else if (e.first == u"s") { style_flags |= TEXT_STRIKETHROUGH; } + else if (e.first == u"o") { style_flags |= TEXT_OVERLINE; } + else if (e.first == u"rb") { ruby = &e.second[0]; } + else { + std::cerr << "ADV: Unrecognized text markup: " << NVL::to_std_string(e.first) << std::endl; + } + } + } } namespace ADVect::Graphics { @@ -279,113 +312,119 @@ namespace ADVect::Graphics { DrawTexture(tex, pos_x, pos_y, w, h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), a_program); } - void RenderStringWalk(const NVL::String& s, i32& pos_x, i32& pos_y, u32 col, u32 style_flags = TEXT_NONE) { + template + void RenderGlyph(NVL::Char c, std::conditional_t pos_x, std::conditional_t pos_y, u32 col, u32 style_flags = TEXT_NONE) { Font* f = ResolveStyleFlags(style_flags); - FT_GlyphSlot& slot = f->face->glyph; + auto& [tx, w, h, l, t, x, y] = f->get_char(c); + + pos_x += l; + + if (auto& buf = tx; DoRender && tx && w != 0) { + DrawTextureStencilAlpha(*buf, pos_x, pos_y - (h - t), w, h); + } + + pos_x += x >> 6; + pos_y += y >> 6; + } + + template + void RenderString(const NVL::String& s, std::conditional_t pos_x, std::conditional_t pos_y, u32 col, u32 style_flags = TEXT_NONE) { for (const auto& c : s) { - f->cache_char(c); - - pos_x += slot->bitmap_left; - f32 ypos = pos_y - (slot->bitmap.rows - slot->bitmap_top); - - if (slot->bitmap.width != 0) { - DrawTextureStencilAlpha((f->cache)[c], pos_x, ypos, slot->bitmap.width, slot->bitmap.rows); - } - - pos_x += slot->advance.x >> 6; - pos_y += slot->advance.y >> 6; + RenderGlyph(c, pos_x, pos_y, col, style_flags); } } - void StringWalk(const NVL::String& s, i32& pos_x, u32 style_flags = TEXT_NONE) { - Font* f = ResolveStyleFlags(style_flags); - - FT_GlyphSlot& slot = f->face->glyph; - for (const auto& c : s) { - f->cache_char(c); - pos_x += slot->bitmap_left; - pos_x += slot->advance.x >> 6; + template + void RenderString(NVL::String::const_iterator cbegin, NVL::String::const_iterator cend, std::conditional_t pos_x, std::conditional_t pos_y, u32 col, u32 style_flags = TEXT_NONE) { + while (cbegin < cend) { + RenderGlyph(*cbegin++, pos_x, pos_y, col, style_flags); } } - void RenderString(const NVL::String& s, i32 pos_x, i32 pos_y, u32 col, u32 style_flags = TEXT_NONE) { - RenderStringWalk(s, pos_x, pos_y, col, style_flags); + void RenderStringCentered(const NVL::String& s, i32 pos_x, i32 pos_y, u32 col, u32 style_flags = TEXT_NONE) { + i32 copy_x = 0, copy_y = 0; + RenderString(s, copy_x, copy_y, col, style_flags); + RenderString(s, pos_x - copy_x / 2, pos_y, col, style_flags); } - void RenderStringWrapWalk(const NVL::String& s, i32& pos_x, i32& pos_y, u32 w, u32 col, u32 style_flags = TEXT_NONE) { + // I cannot reason this function, returns where the string would end if it was not halted + i32 RenderSubstringBox(const NVL::String& s, i32& pos_x, i32& pos_y, i32 reset_x, u32 w, u32 col, u32 style_flags, size_t s_end) { Font* f = ResolveStyleFlags(style_flags); - i32 init_x = pos_x; - size_t last = 0, pos = s.find(u' '); - if (pos == NVL::String::npos) - RenderStringWalk(s, pos_x, pos_y, col, style_flags); - else { - while (last != NVL::String::npos) { - NVL::String sub = s.substr(last, pos - last); - i32 copy_x = pos_x; - StringWalk(sub, copy_x, style_flags); - if (copy_x - init_x > w) { - pos_x = init_x; + NVL::String::const_iterator last = s.cbegin(), end = s.cend(), pos = std::find(last, end, u' '), + halt = s_end > s.length() ? end : last + s_end; + if (pos == end) { // Render the entire thing with hard break if we don't split at all + NVL::String::const_iterator last_copy = last; + i32 copy_x = pos_x, copy_y = pos_y; + while (last != std::min(pos, halt)) { + RenderGlyph(*last++, pos_x, pos_y, col, style_flags); + if (pos_x - reset_x > w) { + pos_x = reset_x; pos_y -= f->face->size->metrics.height / 64; - sub.erase(sub.begin()); } - RenderStringWalk(sub, pos_x, pos_y, col, style_flags); - last = pos; - pos = s.find(u' ', pos + 1); + if (last >= halt) { + RenderString(last_copy, pos, copy_x, copy_y, col, style_flags); + return copy_x; + }; } } - } + else while (last != end) { // Loop through all segments + i32 copy_x = pos_x, old_x = pos_x, copy_y = pos_y; + RenderString(last, pos, copy_x, copy_y, col, style_flags); + if (copy_x - reset_x > w) { // if walk exceeded max w + copy_x = reset_x, copy_y = pos_y; // try walk again from leftmost of box, see if a soft break works; reset y from last walk + RenderString(last, pos, copy_x, copy_y, col, style_flags); + if (copy_x - reset_x > w) { // soft break won't work, go back to old x and hard break + pos_x = old_x; + while (last != pos) { + RenderGlyph(*last++, pos_x, pos_y, col, style_flags); + if (pos_x - reset_x > w) { + pos_x = reset_x; + pos_y -= f->face->size->metrics.height / 64; + } + if (last >= halt) return copy_x; + } + } + else { // soft break and kill beginning space + pos_x = reset_x; + pos_y -= f->face->size->metrics.height / 64; + last++; + RenderString(last, std::min(pos, halt), pos_x, pos_y, col, style_flags); + if (last >= halt) return copy_x; + } + } + else { + RenderString(last, std::min(pos, halt), pos_x, pos_y, col, style_flags); + if (last >= halt) return copy_x; + } - void RenderStringMarkup(const NVL::Environment::MarkupString& ms, i32 pos_x, i32 pos_y, u32 col) { + last = pos; + if (pos != end) + pos = std::find(std::next(pos), end, u' '); + } + return pos_x; + } + + void RenderSubstringMarkupBox(const NVL::Environment::MarkupString& ms, i32 pos_x, i32 pos_y, u32 w, u32 col, size_t end) { + i32 init_x = pos_x; + size_t acc_length = 0; // We assume markups are already sorted for (const NVL::Environment::MarkupSegment& s : ms.segments) { u32 style_flags = TEXT_NONE; const NVL::String* ruby = nullptr; + HandleMarkupFlags(s, style_flags, ruby); - for (const auto& e : s.efs) { - if (e.first == u"b") { style_flags |= TEXT_BOLD; } - else if (e.first == u"i") { style_flags |= TEXT_ITALIC; } - else if (e.first == u"u") { style_flags |= TEXT_UNDERLINE; } - else if (e.first == u"s") { style_flags |= TEXT_STRIKETHROUGH; } - else if (e.first == u"o") { style_flags |= TEXT_OVERLINE; } - else if (e.first == u"rb") { ruby = &e.second[0]; } - else { - std::cerr << "ADV: Unrecognized text markup: " << NVL::to_std_string(e.first) << std::endl; - } + i32 last_x = pos_x, + + segment_end = RenderSubstringBox(s.str, pos_x, pos_y, init_x, w, col, style_flags, end - acc_length); + + if (ruby != nullptr && segment_end != last_x) { + RenderStringCentered(*ruby, segment_end / 2 + last_x / 2, i32(pos_y + 20), col); } - if (ruby != nullptr) { - RenderString(*ruby, pos_x, pos_y + 20, col); - } - - RenderStringWalk(s.str, pos_x, pos_y, col, style_flags); - } - } - - void RenderStringMarkupWrap(const NVL::Environment::MarkupString& ms, i32 pos_x, i32 pos_y, u32 w, u32 col) { - // We assume markups are already sorted - for (const NVL::Environment::MarkupSegment& s : ms.segments) { - u32 style_flags = TEXT_NONE; - const NVL::String* ruby = nullptr; - - for (const auto& e : s.efs) { - if (e.first == u"b") { style_flags |= TEXT_BOLD; } - else if (e.first == u"i") { style_flags |= TEXT_ITALIC; } - else if (e.first == u"u") { style_flags |= TEXT_UNDERLINE; } - else if (e.first == u"s") { style_flags |= TEXT_STRIKETHROUGH; } - else if (e.first == u"o") { style_flags |= TEXT_OVERLINE; } - else if (e.first == u"rb") { ruby = &e.second[0]; } - else { - std::cerr << "ADV: Unrecognized text markup: " << NVL::to_std_string(e.first) << std::endl; - } - } - - if (ruby != nullptr) { - RenderString(*ruby, pos_x, pos_y + 20, col); - } - - RenderStringWrapWalk(s.str, pos_x, pos_y, w, col, style_flags); + acc_length += s.str.length(); + if (acc_length > end) return; } } diff --git a/ADVect/Graphics.h b/ADVect/Graphics.h index d6f25bc..128510b 100644 --- a/ADVect/Graphics.h +++ b/ADVect/Graphics.h @@ -32,10 +32,14 @@ namespace ADVect::Graphics { void DrawTextureImage(const ImageTexture& img, i32 pos_x, i32 pos_y); void DrawTextureImageAlpha(const ImageTexture& img, i32 pos_x, i32 pos_y, f32 alpha); void DrawTextureStencilAlpha(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h); + + template + void RenderGlyph(NVL::Char c, std::conditional_t pos_x, std::conditional_t pos_y, u32 col, u32 style_flags); + template + void RenderString(const NVL::String& s, std::conditional_t pos_x, std::conditional_t pos_y, u32 col, u32 style_flags); + template + void RenderString(NVL::String::const_iterator cbegin, NVL::String::const_iterator cend, std::conditional_t pos_x, std::conditional_t pos_y, u32 col, u32 style_flags); - void RenderStringWalk(const NVL::String& s, i32& pos_x, i32& pos_y, u32 col, u32 style_flags); - void RenderStringWrapWalk(const NVL::String& s, i32& pos_x, i32& pos_y, u32 w, u32 col, u32 style_flags); - void RenderString(const NVL::String& s, i32 pos_x, i32 pos_y, u32 col, u32 style_flags); - void RenderStringMarkup(const NVL::Environment::MarkupString& s, i32 pos_x, i32 pos_y, u32 col); - void RenderStringMarkupWrap(const NVL::Environment::MarkupString& ms, i32 pos_x, i32 pos_y, u32 w, u32 col); + i32 RenderSubstringBox(const NVL::String& s, i32& pos_x, i32& pos_y, i32 reset_x, u32 w, u32 col, u32 style_flags = TEXT_NONE, size_t s_end = NVL::String::npos); + void RenderSubstringMarkupBox(const NVL::Environment::MarkupString& ms, i32 pos_x, i32 pos_y, u32 w, u32 col, size_t end = NVL::String::npos); } diff --git a/ADVect/Track.h b/ADVect/Track.h index 457907b..2b35fd9 100644 --- a/ADVect/Track.h +++ b/ADVect/Track.h @@ -32,7 +32,6 @@ namespace ADVect { } }; - // TODO maybe make this cleaner (packing?) template struct Track { std::conditional_t current{}; @@ -103,9 +102,9 @@ namespace ADVect { void draw_typewriter(u64 current_time, const MarkupTextTransitionTrack& t) { if (t.transition.done) - ADVect::Graphics::RenderStringMarkupWrap(t.current, t.pos_x, t.pos_y, t.w, t.fill); + ADVect::Graphics::RenderSubstringMarkupBox(t.current, t.pos_x, t.pos_y, t.w, t.fill); else - ADVect::Graphics::RenderStringMarkupWrap(t.next.substr(t.transition.get(current_time)), t.pos_x, t.pos_y, t.w, t.fill); + ADVect::Graphics::RenderSubstringMarkupBox(t.next, t.pos_x, t.pos_y, t.w, t.fill, t.transition.get(current_time)); } void draw_image_transition_fade(u64 current_time, const ImageTransitionTrack& t) { diff --git a/ADVect/runtime/dante_ch.nvl b/ADVect/runtime/dante_ch.nvl index 49e7245..c03922b 100644 --- a/ADVect/runtime/dante_ch.nvl +++ b/ADVect/runtime/dante_ch.nvl @@ -1,17 +1,16 @@ BEGIN Inferno -ImageTrack BG Show BG "image.png" +Show BGDialogue "grad.png" <<- [] 由我这里,直通[b]{悲惨之城}。 由我这里,直通[b]{无尽之苦}。 由我这里,直通[b]{堕落众生}。 圣裁于高天激发造我的君主; -造我的大能是[b, rb("C")]{神}的力量,是无上的[b, rb(" L L V M")]{智慧与众爱}所自出。 +造我的大能是[b, rb("C")]{神}的力量,是无上的[b, rb("L L V M")]{智慧与众爱}所自出。 我永远不朽;在我之前,万象 未形,只有永恒的事物存在。 来者呀,快把一切希望弃扬。 我所见到的文字,毫无光彩, 用暗色刻在地狱之门的高处。 -老师呀,讲得真可骇。 [维吉尔] 一切疑虑,必须在这里摆脱。 一切怯懦,必须在这里结束。 diff --git a/NVL/Environment.cpp b/NVL/Environment.cpp index 022520a..d6cf4b1 100644 --- a/NVL/Environment.cpp +++ b/NVL/Environment.cpp @@ -55,7 +55,7 @@ namespace NVL::Environment { Variable Eval(const Parse::Object& obj) { switch (obj.type) { case Parse::Type::Symbol: - return std::get(obj.value); + return ENVIRONMENT.get(std::get(obj.value)); case Parse::Type::Number: return Variable(std::get(obj.value)); case Parse::Type::String: @@ -76,9 +76,14 @@ namespace NVL::Environment { Variable Apply(const Parse::Command& c) { std::vector args{}; - for (u32 i = 1; i < c.size(); i++) - args.push_back(Eval(c[i])); - return std::get)>>(ENVIRONMENT.get(std::get(c[0].value)).value)(args); + + Variable f = Eval(c[0]); + u32 i = 1; + while (i < c.size()) { + args.push_back(Eval(c[i++])); + } + if (f.length != -1 && f.length != i - 1) throw std::runtime_error("Function arity mismatch"); + return std::get)>>(f.value)(args); } void EvalScene(const Parse::Scene& s) {