#include "./engine_tools.hpp" #include #include #include #include #include namespace MM::Services { using UpdateStrategies::update_key_t; template static void renderUpdateStratGraph( const std::unordered_map>& g, std::set& a, NColFn&& node_color_fn, LColFn&& line_color_fn, TTFn&& tooltip_fn ) { using UpdateStrategies::update_key_t; std::set work_queue{a.begin(), a.end()}; std::vector> node_table{}; node_table.push_back({}); std::unordered_map> node_table_pos; // THX to the schwammal { // phase one, extract dep less nodes for (auto it = work_queue.begin(); it != work_queue.end();) { bool no_deps = g.at(*it).empty(); if (!no_deps) { for (auto dep : g.at(*it)) { if (a.count(dep)) { no_deps = false; break; } } } if (no_deps) { node_table_pos[*it] = {0, node_table[0].size()}; node_table[0].emplace_back(*it); it = work_queue.erase(it); } else { it++; } } } { // phase two, do rest size_t curr_col = 0; while (!work_queue.empty()) { curr_col++; node_table.push_back({}); std::vector nodes_this_round{}; for (auto it = work_queue.begin(); it != work_queue.end();) { bool deps_resolved = true; const auto& it_deps = g.at(*it); for (const update_key_t dep : it_deps) { if (a.count(dep) && work_queue.count(dep)) { deps_resolved = false; break; } } if (deps_resolved) { //node_table_pos[*it] = {curr_col, node_table[curr_col].size()}; //node_table[curr_col].emplace_back(*it); //it = work_queue.erase(it); nodes_this_round.push_back(*it); } else { //it++; } it++; } // apply nodes for (auto it : nodes_this_round) { node_table_pos[it] = {curr_col, node_table[curr_col].size()}; node_table[curr_col].emplace_back(it); work_queue.erase(it); } } } { // display //ImGui::BeginChild("canvas", {0, 0}, true); const ImVec2 cp = ImGui::GetCursorScreenPos(); const auto max_cont = ImGui::GetWindowContentRegionMax(); ImGui::Dummy(max_cont); auto* dl = ImGui::GetWindowDrawList(); const float circle_radius = 20.f; const float circle_padding = 40.f; // lines for (const update_key_t node : a) { for (const update_key_t dep : g.at(node)) { if (!a.count(dep)) { continue; } ImVec2 p_start{ cp.x + node_table_pos[node].first*(2*circle_radius+circle_padding) + circle_radius, cp.y + node_table_pos[node].second*(2*circle_radius+circle_padding) + circle_radius, }; ImVec2 p_end{ cp.x + node_table_pos[dep].first*(2*circle_radius+circle_padding) + circle_radius, cp.y + node_table_pos[dep].second*(2*circle_radius+circle_padding) + circle_radius, }; dl->AddLine(p_start, p_end, ImGui::ColorConvertFloat4ToU32(line_color_fn(node, dep))); } } // nodes for (size_t curr_col = 0; curr_col < node_table.size(); curr_col++) { const auto& curr_table_col = node_table[curr_col]; for (size_t curr_row = 0; curr_row < curr_table_col.size(); curr_row++) { ImVec2 p{ cp.x + curr_col*(2*circle_radius+circle_padding) + circle_radius, cp.y + curr_row*(2*circle_radius+circle_padding) + circle_radius, }; //dl->AddCircleFilled(p, circle_radius, IM_COL32(100, 100, 100, 100)); auto col_vec = node_color_fn(curr_table_col.at(curr_row)); ImVec4 col_vec_bg = col_vec; col_vec_bg.w *= 0.2f; dl->AddCircleFilled(p, circle_radius, ImGui::ColorConvertFloat4ToU32(col_vec_bg)); dl->AddCircle(p, circle_radius, ImGui::ColorConvertFloat4ToU32(col_vec)); if (ImGui::IsMouseHoveringRect( {p.x - circle_radius, p.y - circle_radius}, {p.x + circle_radius, p.y + circle_radius} )) { //ImGui::SetTooltip("'%s'\n[%u]", , curr_table_col.at(curr_row)); tooltip_fn(curr_table_col.at(curr_row)); } } } //ImGui::EndChild(); } } bool ImGuiEngineTools::enable(Engine& engine, std::vector& task_array) { auto& menu_bar = engine.getService(); // use underscore hack to make it last menu_bar.menu_tree["Engine"]["_Stop Engine"] = [](Engine& e) { ImGui::Separator(); if (ImGui::MenuItem("Stop Engine")) { e.stop(); } }; menu_bar.menu_tree["Engine"]["About"] = [this](Engine&) { ImGui::MenuItem("About", NULL, &_show_about); }; menu_bar.menu_tree["Engine"]["Services"] = [this](Engine&) { ImGui::MenuItem("Services", NULL, &_show_services); }; menu_bar.menu_tree["Engine"]["UpdateStrategy"] = [this](Engine&) { ImGui::MenuItem("UpdateStrategy", NULL, &_show_update_stategy); }; // add task task_array.push_back( UpdateStrategies::TaskInfo{"ImGuiEngineTools::render"} .fn([this](Engine& e){ renderImGui(e); }) .phase(UpdateStrategies::update_phase_t::PRE) .succeed("ImGuiMenuBar::render") ); return true; } void ImGuiEngineTools::disable(Engine& engine) { auto& menu_bar = engine.getService(); menu_bar.menu_tree["Engine"].erase("_Stop Engine"); menu_bar.menu_tree["Engine"].erase("About"); menu_bar.menu_tree["Engine"].erase("Services"); menu_bar.menu_tree["Engine"].erase("UpdateStrategy"); } void ImGuiEngineTools::renderImGui(Engine& engine) { if (_show_about) { renderAbout(engine); } if (_show_services) { renderServices(engine); } if (_show_update_stategy) { renderUpdateStrategy(engine); } } void ImGuiEngineTools::renderAbout(Engine& engine) { if (ImGui::Begin("About##EngineTools", &_show_about)) { ImGui::Text("TODO"); ImGui::Text("MushMachine"); ImGui::Text("UpdateStrategy: %s", engine.getUpdateStrategy().name()); ImGui::Text("EnTT v%d.%d.%d", ENTT_VERSION_MAJOR, ENTT_VERSION_MINOR, ENTT_VERSION_PATCH); } ImGui::End(); } void ImGuiEngineTools::renderServices(Engine& engine) { if (ImGui::Begin("Services##EngineTools", &_show_services)) { ImGui::Text("TODO: make sortable"); ImGui::Checkbox("edit mode", &_services_edit_mode); if (ImGui::BeginTable( "services_table", 3, ImGuiTableFlags_RowBg //| )) { ImGui::TableSetupColumn("id", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("name"); ImGui::TableSetupColumn("status"); ImGui::TableHeadersRow(); for (auto& it : engine._services) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%u", it.first); ImGui::TableNextColumn(); ImGui::Text("%s", it.second->second->name()); ImGui::TableNextColumn(); if (_services_edit_mode) { ImGui::PushID(it.first); if (ImGui::SmallButton(it.second->first ? "enabled" : "disabled")) { if (it.second->first) { engine.disableService(it.first); } else { engine.enableService(it.first); } } //ImGui::SetTooltip("click to toggle!"); the heck? ImGui::PopID(); } else { ImGui::Text("%s", it.second->first ? "enabled" : "disabled"); } } ImGui::EndTable(); } } ImGui::End(); } void ImGuiEngineTools::renderUpdateStrategy(Engine& engine) { if (ImGui::Begin("UpdateStrategy##EngineTools", &_show_update_stategy, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { auto& us = engine.getUpdateStrategy(); auto us_name = us.name(); ImGui::Text("UpdateStrategy: '%s'", us_name); // TODO: tabs? dropdown(combo)? static const char* const phase_str[4] = { "All", "Pre", "Main", "Post", }; static int curr_phase = 2; ImGui::Combo("Phase", &curr_phase, phase_str, 4); if (ImGui::BeginTabBar("tabs")) { if (curr_phase != 0 && ImGui::BeginTabItem("Graph")) { // BRUHHH this is needs to be cached std::unordered_map> graph; std::unordered_map task_lut; std::set nodes; // build lut us.forEachTask([&graph, &nodes, &task_lut](UpdateStrategies::TaskInfo& task) -> bool { if (task._phase != UpdateStrategies::update_phase_t(curr_phase-1)) { return true; // skip, not our phase } graph[task._key] = task._dependencies; task_lut[task._key] = &task; nodes.emplace(task._key); return true; }); // also do dependents // ugh, 2*N us.forEachTask([&graph](UpdateStrategies::TaskInfo& task) -> bool { if (task._phase != UpdateStrategies::update_phase_t(curr_phase-1)) { return true; // skip, not our phase } for (const auto it : task._dependents) { graph[it].emplace(task._key); } return true; }); // TODO: make sense of the colloring renderUpdateStratGraph(graph, nodes, [](const update_key_t key) -> ImVec4 { return {0.9f, 1.f, 0.9f, 1.f}; }, [](const update_key_t from, const update_key_t to) -> ImVec4 { return {0.9f, 1.f, 0.9f, 1.f}; }, [&task_lut](update_key_t key) { ImGui::SetTooltip("%s\n[%u]", task_lut.at(key)->_name.c_str(), key); } ); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("List")) { if (ImGui::BeginTable("table", curr_phase == 0 ? 3 : 2)) { us.forEachTask([](UpdateStrategies::TaskInfo& task) -> bool { if ( curr_phase == 0 || task._phase == UpdateStrategies::update_phase_t(curr_phase-1) ) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%u", task._key); if (curr_phase == 0) { ImGui::TableNextColumn(); ImGui::Text("%s", phase_str[task._phase + 1]); } ImGui::TableNextColumn(); ImGui::Text("%s", task._name.c_str()); } return true; }); ImGui::EndTable(); } ImGui::EndTabItem(); } ImGui::EndTabBar(); } #if 0 auto& g = us_default->getGraph(UpdateStrategies::update_phase_t::MAIN); auto& a = us_default->getActiveSet(UpdateStrategies::update_phase_t::MAIN); std::set nodes; for (const auto& it : g) { nodes.emplace(it.first); } if (ImGui::BeginTabBar("tabs")) { if (ImGui::BeginTabItem("table")) { if (ImGui::BeginTable("table", 2)) { for (const auto it : nodes) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%u", it); ImGui::TableNextColumn(); ImGui::Text("%s", us_default->_tasks.at(it)._name.c_str()); } ImGui::EndTable(); } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("graph")) { renderUpdateStratGraph(g, nodes, [&a](const update_key_t key) -> ImVec4 { if (a.count(key)) { return {0.9f, 1.f, 0.9f, 1.f}; } else { return {0.4f, 0.2f, 0.2f, 1.f}; } }, [&a](const update_key_t from, const update_key_t to) -> ImVec4 { if (a.count(from) && a.count(to)) { return {0.9f, 1.f, 0.9f, 1.f}; } else { return {0.4f, 0.2f, 0.2f, 1.f}; } }, [us_default](update_key_t key) { ImGui::SetTooltip("'%s'\n[%u]", us_default->_tasks.at(key)._name.c_str(), key); }); ImGui::EndTabItem(); } ImGui::EndTabBar(); } } #endif } ImGui::End(); } } // namespace MM::Services