From 9ca342ddc3ec4f06678873fb6bba5280fe7591f8 Mon Sep 17 00:00:00 2001 From: Anthony Stewart Date: Wed, 2 Jul 2025 23:01:12 -0500 Subject: [PATCH] Display logic expressions in a table. --- .../randomizer/logic_expression.cpp | 42 +-- .../randomizer/logic_expression.h | 13 +- .../randomizer/randomizer_logic_tracker.cpp | 255 +++++++++++------- 3 files changed, 196 insertions(+), 114 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/logic_expression.cpp b/soh/soh/Enhancements/randomizer/logic_expression.cpp index 30e0b5c72..e5962a0a8 100644 --- a/soh/soh/Enhancements/randomizer/logic_expression.cpp +++ b/soh/soh/Enhancements/randomizer/logic_expression.cpp @@ -355,14 +355,15 @@ class Parser { #pragma endregion -std::unique_ptr LogicExpression::Parse(const std::string& exprStr) { +std::shared_ptr LogicExpression::Parse(const std::string& exprStr) { Parser parser(exprStr); std::shared_ptr impl = parser.Parse(); - std::function(const std::shared_ptr&)> populateChildren; + std::function(const std::shared_ptr&)> populateChildren; populateChildren = [&](const std::shared_ptr& impl) { - auto expr = std::make_unique(); + auto expr = std::make_shared(); expr->impl = impl; + impl->expression = expr; for (const auto& child : impl->children) { expr->children.emplace_back(populateChildren(child)); } @@ -372,7 +373,7 @@ std::unique_ptr LogicExpression::Parse(const std::string& exprS return populateChildren(impl); } -const std::vector>& LogicExpression::GetChildren() const { +const std::vector>& LogicExpression::GetChildren() const { return children; } @@ -561,8 +562,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::EvaluateFunction(const std: // If callback is provided, call it with function info if (callback) { - std::string exprStr = functionName + "(" + (children.empty() ? "" : "...") + ")"; - callback(exprStr, path, depth, GetTypeString(), result); + callback(expression, path, depth, GetTypeString(), result); } return result; @@ -854,7 +854,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::EvaluateArithmetic(char op, exprStr = "Unknown expression"; } - callback(exprStr, path, depth, opStr, result); + callback(expression, path, depth, opStr, result); } return result; @@ -902,7 +902,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& } if (callback) { - callback(exprText, path, depth, GetTypeString(), result); + callback(expression, path, depth, GetTypeString(), result); } return result; @@ -913,7 +913,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& auto childResult = children[0]->Evaluate(path + ".0", depth + 1, callback); result = !GetValue(childResult); if (callback) { - callback(exprText, path, depth, GetTypeString(), result); + callback(expression, path, depth, GetTypeString(), result); } return result; } @@ -924,7 +924,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& if (!GetValue(leftResult)) { result = false; if (callback) { - callback(exprText, path, depth, GetTypeString() + " (short-circuit)", result); + callback(expression, path, depth, GetTypeString() + " (short-circuit)", result); } return result; } @@ -932,7 +932,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& auto rightResult = children[1]->Evaluate(path + ".1", depth + 1, callback); result = GetValue(rightResult); if (callback) { - callback(exprText, path, depth, GetTypeString(), result); + callback(expression, path, depth, GetTypeString(), result); } return result; } @@ -943,7 +943,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& if (GetValue(leftResult)) { result = true; if (callback) { - callback(exprText, path, depth, GetTypeString() + " (short-circuit)", result); + callback(expression, path, depth, GetTypeString() + " (short-circuit)", result); } return result; } @@ -951,7 +951,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& auto rightResult = children[1]->Evaluate(path + ".1", depth + 1, callback); result = GetValue(rightResult); if (callback) { - callback(exprText, path, depth, GetTypeString(), result); + callback(expression, path, depth, GetTypeString(), result); } return result; } @@ -997,7 +997,7 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& try { result = std::visit(compare, leftResult, rightResult); if (callback) { - callback(exprText, path, depth, GetTypeString(), result); + callback(expression, path, depth, GetTypeString(), result); } return result; } catch (const std::bad_variant_access&) { @@ -1025,7 +1025,8 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& } if (callback) { - callback(exprText, path, depth, GetTypeString() + (cond ? " (true branch)" : " (false branch)"), result); + callback(expression, path, depth, GetTypeString() + (cond ? " (true branch)" : " (false branch)"), + result); } return result; } @@ -1038,16 +1039,19 @@ LogicExpression::ValueVariant LogicExpression::Impl::Evaluate(const std::string& } ExpressionEvaluation EvaluateExpression(std::string condition) { - const auto& expression = LogicExpression::Parse(condition); + return EvaluateExpression(LogicExpression::Parse(condition)); +} +ExpressionEvaluation EvaluateExpression(std::shared_ptr expression) { // Create a vector to store the evaluation sequence - std::vector> + std::vector, std::string, int, std::string, LogicExpression::ValueVariant>> evaluationSequence; // Define a callback that records each evaluation step - auto recordCallback = [&evaluationSequence](const std::string& exprStr, const std::string& path, int depth, + auto recordCallback = [&evaluationSequence](const std::shared_ptr& expr, + const std::string& path, int depth, const std::string& type, const LogicExpression::ValueVariant& result) { - evaluationSequence.emplace_back(exprStr, path, depth, type, result); + evaluationSequence.emplace_back(expr, path, depth, type, result); }; // Evaluate the expression with the callback diff --git a/soh/soh/Enhancements/randomizer/logic_expression.h b/soh/soh/Enhancements/randomizer/logic_expression.h index c8681469c..35eb40faf 100644 --- a/soh/soh/Enhancements/randomizer/logic_expression.h +++ b/soh/soh/Enhancements/randomizer/logic_expression.h @@ -14,11 +14,11 @@ class LogicExpression { public: using ValueVariant = std::variant; // Define callback type for evaluation tracing - using EvaluationCallback = std::function; + using EvaluationCallback = std::function&, const std::string&, int, const std::string&, const ValueVariant&)>; - static std::unique_ptr Parse(const std::string& exprStr); + static std::shared_ptr Parse(const std::string& exprStr); std::string ToString() const; - const std::vector>& GetChildren() const; + const std::vector>& GetChildren() const; // Add optional callback parameter to Evaluate template T Evaluate(const EvaluationCallback& callback = nullptr) const { @@ -68,6 +68,7 @@ class LogicExpression { Impl* parent; size_t startIndex; size_t endIndex; + std::shared_ptr expression; // Modified to accept path and depth ValueVariant Evaluate(const std::string& path = "0", int depth = 0, const EvaluationCallback& callback = nullptr) const; @@ -293,14 +294,14 @@ class LogicExpression { }; std::shared_ptr impl; - std::vector> children; + std::vector> children; friend class Parser; friend bool IsEnumConstant(const std::string& s); }; struct ExpressionEvaluation { - std::string Expression; + std::shared_ptr Expression; int Depth; std::string Type; LogicExpression::ValueVariant Result; @@ -309,4 +310,6 @@ struct ExpressionEvaluation { ExpressionEvaluation EvaluateExpression(std::string condition); +ExpressionEvaluation EvaluateExpression(std::shared_ptr expression); + std::string ToString(const LogicExpression::ValueVariant& value); \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp index 4379a875e..34e8ebe59 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_logic_tracker.cpp @@ -15,11 +15,21 @@ uint64_t GetUnixTimestamp(); struct LogicTrackerCheck { struct Region { + struct ExpressionRow { + std::shared_ptr Expression; + std::vector Children; + std::optional ChildDay; + std::optional ChildNight; + std::optional AdultDay; + std::optional AdultNight; + bool Expanded; + }; + std::string RegionName; - std::unique_ptr ChildDay; - std::unique_ptr ChildNight; - std::unique_ptr AdultDay; - std::unique_ptr AdultNight; + ExpressionRow Root; + bool CombineAll = false; + bool CombineChild = false; + bool CombineAdult = false; }; std::string CheckName; @@ -28,6 +38,61 @@ struct LogicTrackerCheck { static std::vector checks; +LogicTrackerCheck::Region::ExpressionRow CreateExpressionRows(const std::shared_ptr& expression) { + LogicTrackerCheck::Region::ExpressionRow row; + row.Expression = expression; + row.Expanded = false; + + const auto& children = expression->GetChildren(); + row.Children.reserve(children.size()); + for (const auto& child : children) { + row.Children.emplace_back(CreateExpressionRows(child)); + } + + return row; +} + +enum class AgeTime { + ChildDay, + ChildNight, + AdultDay, + AdultNight +}; + + +static void PopulateExpressionValues(LogicTrackerCheck::Region::ExpressionRow& row, const ExpressionEvaluation& eval, AgeTime ageTime) { + if (ageTime == AgeTime::ChildDay) { + row.ChildDay = eval.Result; + } else if (ageTime == AgeTime::ChildNight) { + row.ChildNight = eval.Result; + } else if (ageTime == AgeTime::AdultDay) { + row.AdultDay = eval.Result; + } else if (ageTime == AgeTime::AdultNight) { + row.AdultNight = eval.Result; + } + + for (auto& rowChild : row.Children) { + for (const auto& evalChild : eval.Children) { + if (row.Expression == eval.Expression) { + PopulateExpressionValues(rowChild, evalChild, ageTime); + } + } + } +} + +static std::tuple CalculateCombines(const LogicTrackerCheck::Region::ExpressionRow& row) { + bool combineChild = row.ChildDay == row.ChildNight; + bool combineAdult = row.AdultDay == row.AdultNight; + bool combineAll = combineChild && combineAdult && row.ChildDay == row.AdultDay; + for (const auto& child : row.Children) { + auto [childCombineAll, childCombineChild, childCombineAdult] = CalculateCombines(child); + combineAll &= childCombineAll; + combineChild &= childCombineChild; + combineAdult &= childCombineAdult; + } + return {combineAll, combineChild, combineAdult}; +} + void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { checks.clear(); @@ -41,13 +106,15 @@ void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { if (locationAccess.GetLocation() == check) { LogicTrackerCheck::Region regionAgeTime; regionAgeTime.RegionName = region.regionName; + regionAgeTime.Root = CreateExpressionRows(LogicExpression::Parse(locationAccess.GetConditionStr())); + regionAgeTime.Root.Expanded = true; if (region.childDay) { logic->IsChild = true; logic->AtDay = true; - regionAgeTime.ChildDay = - std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + const auto& eval = EvaluateExpression(regionAgeTime.Root.Expression); + PopulateExpressionValues(regionAgeTime.Root, eval, AgeTime::ChildDay); logic->IsChild = false; logic->AtDay = false; @@ -56,8 +123,8 @@ void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { logic->IsChild = true; logic->AtNight = true; - regionAgeTime.ChildNight = - std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + const auto& eval = EvaluateExpression(regionAgeTime.Root.Expression); + PopulateExpressionValues(regionAgeTime.Root, eval, AgeTime::ChildNight); logic->IsChild = false; logic->AtNight = false; @@ -66,8 +133,8 @@ void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { logic->IsAdult = true; logic->AtDay = true; - regionAgeTime.AdultDay = - std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + const auto& eval = EvaluateExpression(regionAgeTime.Root.Expression); + PopulateExpressionValues(regionAgeTime.Root, eval, AgeTime::AdultDay); logic->IsAdult = false; logic->AtDay = false; @@ -76,13 +143,18 @@ void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { logic->IsAdult = true; logic->AtNight = true; - regionAgeTime.AdultNight = - std::make_unique(EvaluateExpression(locationAccess.GetConditionStr())); + const auto& eval = EvaluateExpression(regionAgeTime.Root.Expression); + PopulateExpressionValues(regionAgeTime.Root, eval, AgeTime::AdultNight); logic->IsAdult = false; logic->AtNight = false; } + auto [combineAll, combineChild, combineAdult] = CalculateCombines(regionAgeTime.Root); + regionAgeTime.CombineAll = combineAll; + regionAgeTime.CombineChild = combineChild; + regionAgeTime.CombineAdult = combineAdult; + logicTrackerCheck.Regions.emplace_back(std::move(regionAgeTime)); } } @@ -90,110 +162,113 @@ void LogicTrackerWindow::ShowRandomizerCheck(RandomizerCheck check) { checks.emplace_back(std::move(logicTrackerCheck)); - Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Logic Tracker")->Show(); + auto window = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Logic Tracker"); + window->Show(); + ImGui::SetWindowFocus(window->GetName().c_str()); } -std::string TruncateText(const std::string& text, float maxWidth) { - ImVec2 textSize = ImGui::CalcTextSize(text.c_str()); - if (textSize.x <= maxWidth) - return text; - - std::string truncated = text; - // Remove characters until the text fits with the ellipsis appended - while (!truncated.empty() && ImGui::CalcTextSize((truncated + "...").c_str()).x > maxWidth) { - truncated.pop_back(); +static std::string ToString(const std::optional& value) { + if (!value.has_value()) { + return ""; } - return truncated + "..."; + return ToString(value.value()); } -static void DrawExpression(const ExpressionEvaluation& expression) { - ImGuiTreeNodeFlags treeNodeFlags = - expression.Children.empty() ? ImGuiTreeNodeFlags_Leaf : ImGuiTreeNodeFlags_DefaultOpen; +static void DrawExpressionRow(const LogicTrackerCheck::Region& region, LogicTrackerCheck::Region::ExpressionRow& row, + int level) { + ImGui::TableNextRow(); + if (level > 0) { + ImGui::Indent(10.0f); + } - float availableWidth = ImGui::GetContentRegionAvail().x - ImGui::GetTreeNodeToLabelSpacing(); - auto text = TruncateText(ToString(expression.Result) + " = " + expression.Expression, availableWidth); + ImGui::TableNextColumn(); + ImGui::Text("%d", level); - if (ImGui::TreeNodeEx(&expression, treeNodeFlags, "%s", text.c_str())) { - if (!expression.Children.empty()) { - for (const auto& child : expression.Children) { - DrawExpression(child); - } + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s", row.Expression->ToString().c_str()); + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(ToString(row.ChildDay).c_str()); + + if (!region.CombineAll) { + if (!region.CombineChild) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(ToString(row.ChildNight).c_str()); } - ImGui::TreePop(); + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(ToString(row.AdultDay).c_str()); + + if (!region.CombineAdult) { + ImGui::TableNextColumn(); + ImGui::TextUnformatted(ToString(row.AdultNight).c_str()); + } + } + + for (auto& child : row.Children) { + //if (child.Expanded) { + DrawExpressionRow(region, child, level + 1); + //} + } + + if (level > 0) { + ImGui::Unindent(10.0f); } } -static void DrawCheckRegion(const LogicTrackerCheck::Region& region) { - if (ImGui::TreeNodeEx(region.RegionName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { - if (region.ChildDay != nullptr) { - bool resultString = LogicExpression::GetValue(region.ChildDay->Result); - if (ImGui::TreeNodeEx(region.ChildDay.get(), ImGuiTreeNodeFlags_DefaultOpen, "Child-Day Result: %s", - resultString ? "true" : "false")) { - DrawExpression(*region.ChildDay); - ImGui::TreePop(); - } - } else { - if (ImGui::TreeNodeEx("Child-Day Inaccessible", ImGuiTreeNodeFlags_Leaf)) { - ImGui::TreePop(); - } +static void DrawCheckRegion(LogicTrackerCheck::Region& region) { + int columnCount = 3; + if (!region.CombineAll) { + if (!region.CombineChild) { + columnCount += 1; } - - if (region.ChildNight != nullptr) { - bool resultString = LogicExpression::GetValue(region.ChildNight->Result); - if (ImGui::TreeNodeEx(region.ChildNight.get(), ImGuiTreeNodeFlags_DefaultOpen, "Child-Night Result: %s", - resultString ? "true" : "false")) { - DrawExpression(*region.ChildNight); - ImGui::TreePop(); - } - } else { - if (ImGui::TreeNodeEx("Child-Night Inaccessible", ImGuiTreeNodeFlags_Leaf)) { - ImGui::TreePop(); - } + columnCount += 1; + if (!region.CombineAdult) { + columnCount += 1; } - - if (region.AdultDay != nullptr) { - bool resultString = LogicExpression::GetValue(region.AdultDay->Result); - if (ImGui::TreeNodeEx(region.AdultDay.get(), ImGuiTreeNodeFlags_DefaultOpen, "Adult-Day Result: %s", - resultString ? "true" : "false")) { - DrawExpression(*region.AdultDay); - ImGui::TreePop(); - } - } else { - if (ImGui::TreeNodeEx("Adult-Day Inaccessible", ImGuiTreeNodeFlags_Leaf)) { - ImGui::TreePop(); - } - } - - if (region.AdultNight != nullptr) { - bool resultString = LogicExpression::GetValue(region.AdultNight->Result); - if (ImGui::TreeNodeEx(region.AdultNight.get(), ImGuiTreeNodeFlags_DefaultOpen, "Adult-Night Result: %s", - resultString ? "true" : "false")) { - DrawExpression(*region.AdultNight); - ImGui::TreePop(); - } - } else { - if (ImGui::TreeNodeEx("Adult-Night Inaccessible", ImGuiTreeNodeFlags_Leaf)) { - ImGui::TreePop(); - } - } - - ImGui::TreePop(); } + + if (ImGui::BeginTable(region.RegionName.c_str(), columnCount, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Expression", ImGuiTableColumnFlags_WidthStretch); + if (region.CombineAll) { + ImGui::TableSetupColumn("All", ImGuiTableColumnFlags_WidthFixed); + } else { + if (region.CombineChild) { + ImGui::TableSetupColumn("Child", ImGuiTableColumnFlags_WidthFixed); + } else { + ImGui::TableSetupColumn("Child Day", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Child Night", ImGuiTableColumnFlags_WidthFixed); + } + if (region.CombineAdult) { + ImGui::TableSetupColumn("Adult", ImGuiTableColumnFlags_WidthFixed); + } else { + ImGui::TableSetupColumn("Adult Day", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Adult Night", ImGuiTableColumnFlags_WidthFixed); + } + } + ImGui::TableHeadersRow(); + } + + DrawExpressionRow(region, region.Root, 0); + + ImGui::EndTable(); } -static void DrawCheck(const LogicTrackerCheck& check) { +static void DrawCheck(LogicTrackerCheck& check) { ImGui::SeparatorText(("Check: " + check.CheckName).c_str()); if (check.Regions.empty()) { ImGui::Text("No regions found for this check."); return; } - for (const auto& region : check.Regions) { + for (auto& region : check.Regions) { DrawCheckRegion(region); } } void LogicTrackerWindow::DrawElement() { - for (const auto& check : checks) { + for (auto& check : checks) { DrawCheck(check); } }