248 lines
9.2 KiB
C++
248 lines
9.2 KiB
C++
#pragma once
|
|
|
|
#include "Gallery_Observer_Adminive.h"
|
|
#include "Gallery_Renderables.h"
|
|
|
|
#include <renderive/scene/base/Scene_Base.hpp>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <charconv>
|
|
#include <concepts>
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace renderive::web {
|
|
|
|
class Gallery_Controls final {
|
|
public:
|
|
using Json = nlohmann::json;
|
|
|
|
template <class Object>
|
|
void add(Object& object) {
|
|
const auto descriptor = adminive::Type_Descriptor<Object>::get();
|
|
add(descriptor.name(), descriptor.label(), object);
|
|
}
|
|
|
|
template <class Object>
|
|
void add(std::string id, Object& object) {
|
|
add(std::move(id), adminive::Type_Descriptor<Object>::get().label(), object);
|
|
}
|
|
|
|
[[nodiscard]] Json resources() const {
|
|
Json result = Json::array();
|
|
for (const auto& entry : entries_) {
|
|
Json resource = entry.resource();
|
|
resource["target"] = entry.id;
|
|
resource["view"]["title"] = entry.title;
|
|
result.push_back(std::move(resource));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] Json observers(const Scene_Base& scene) const {
|
|
Json result = Json::array();
|
|
const auto topology = scene.topology_snapshot();
|
|
for (std::size_t index = 0; index < topology.renderables.size(); ++index) {
|
|
const auto* renderable =
|
|
dynamic_cast<const renderive::Renderable*>(topology.renderables[index].get());
|
|
if (!renderable)
|
|
continue;
|
|
const Entry* entry = find(renderable);
|
|
std::string title = entry ? entry->title : renderable->object_name();
|
|
if (title.empty())
|
|
title = "渲染节点 " + std::to_string(index + 1);
|
|
Json resource{
|
|
{"descriptor", adminive::to_descriptor_json<
|
|
Json, renderive::Renderable_Observation>()},
|
|
{"data", adminive::to_frontend_json<Json>(renderable->observation())},
|
|
{"target", entry ? entry->id : node_id(index)},
|
|
{"title", std::move(title)}
|
|
};
|
|
result.push_back(std::move(resource));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] Json task_graph(const Scene_Base& scene) const {
|
|
const auto topology = scene.topology_snapshot();
|
|
const auto paint_order = scene.paint_order_snapshot();
|
|
std::unordered_map<const Renderable_Base*, std::size_t> indices;
|
|
indices.reserve(topology.renderables.size());
|
|
for (std::size_t index = 0; index < topology.renderables.size(); ++index)
|
|
indices.emplace(topology.renderables[index].get(), index);
|
|
|
|
std::unordered_map<const Renderable_Base*, std::size_t> paint_indices;
|
|
paint_indices.reserve(paint_order.size());
|
|
for (std::size_t index = 0; index < paint_order.size(); ++index)
|
|
paint_indices.emplace(paint_order[index].get(), index + 1);
|
|
|
|
Json nodes = Json::array();
|
|
Json edges = Json::array();
|
|
Json ordered = Json::array();
|
|
for (std::size_t index = 0; index < topology.renderables.size(); ++index) {
|
|
const auto* base = topology.renderables[index].get();
|
|
const auto* renderable = dynamic_cast<const renderive::Renderable*>(base);
|
|
const Entry* entry = find(base);
|
|
std::string label = entry ? entry->title : std::string{};
|
|
if (label.empty() && renderable)
|
|
label = renderable->object_name();
|
|
if (label.empty())
|
|
label = "渲染节点 " + std::to_string(index + 1);
|
|
Json node{
|
|
{"id", node_id(index)},
|
|
{"label", std::move(label)},
|
|
{"kind", entry ? "control" : "layer"},
|
|
{"paint_order", paint_indices.contains(base) ? paint_indices.at(base) : 0}
|
|
};
|
|
if (entry)
|
|
node["target"] = entry->id;
|
|
nodes.push_back(std::move(node));
|
|
}
|
|
|
|
append_relationships(edges, topology.display, indices, "display");
|
|
append_relationships(edges, topology.dependency, indices, "dependency");
|
|
for (const auto& renderable : paint_order)
|
|
ordered.push_back(node_id(indices.at(renderable.get())));
|
|
|
|
Json control_graphs = Json::array();
|
|
for (const auto& entry : entries_) {
|
|
if (!entry.renderable)
|
|
continue;
|
|
control_graphs.push_back({
|
|
{"target", entry.id},
|
|
{"title", entry.title},
|
|
{"graph", renderable_task_graph(*entry.renderable)}
|
|
});
|
|
}
|
|
|
|
Json result{
|
|
{"nodes", std::move(nodes)},
|
|
{"edges", std::move(edges)},
|
|
{"paint_order", std::move(ordered)},
|
|
{"renderables", std::move(control_graphs)}
|
|
};
|
|
result["topology_id"] = topology_id(result);
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] adminive::Update_Result apply(std::string_view target,
|
|
const Json& patch) const {
|
|
for (const auto& entry : entries_) {
|
|
if (entry.id == target)
|
|
return entry.apply(patch);
|
|
}
|
|
adminive::Update_Result result;
|
|
result.success = false;
|
|
result.message = "unknown gallery control target: " + std::string(target);
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
struct Entry {
|
|
std::string id;
|
|
std::string title;
|
|
std::function<Json()> resource;
|
|
std::function<adminive::Update_Result(const Json&)> apply;
|
|
renderive::Renderable* renderable{};
|
|
};
|
|
|
|
template <class Object>
|
|
void add(std::string id, std::string title, Object& object) {
|
|
using Model = adminive::Object_Model_Type<Object>;
|
|
auto* target = &object;
|
|
renderive::Renderable* renderable{};
|
|
if constexpr (std::derived_from<Object, renderive::Renderable>)
|
|
renderable = target;
|
|
entries_.push_back({
|
|
std::move(id), std::move(title),
|
|
[target] {
|
|
return Json{
|
|
{"descriptor", adminive::to_descriptor_json<Json, Object>()},
|
|
{"view", adminive::to_view_json<Json, Model>(
|
|
adminive::describe_edit_view<Model>())},
|
|
{"data", adminive::to_frontend_json<Json>(*target)}
|
|
};
|
|
},
|
|
[target](const Json& patch) {
|
|
return adminive::apply_frontend_patch<Json>(*target, patch);
|
|
},
|
|
renderable
|
|
});
|
|
}
|
|
|
|
[[nodiscard]] const Entry* find(const Renderable_Base* renderable) const noexcept {
|
|
const auto found = std::find_if(entries_.begin(), entries_.end(),
|
|
[renderable](const Entry& entry) { return entry.renderable == renderable; });
|
|
return found == entries_.end() ? nullptr : &*found;
|
|
}
|
|
|
|
static std::string node_id(std::size_t index) {
|
|
return "renderable-" + std::to_string(index);
|
|
}
|
|
|
|
static void append_relationships(
|
|
Json& edges,
|
|
const std::vector<Scene_Base::Topology_Relationship>& relationships,
|
|
const std::unordered_map<const Renderable_Base*, std::size_t>& indices,
|
|
std::string_view kind) {
|
|
for (const auto& relationship : relationships) {
|
|
if (!relationship.parent)
|
|
continue;
|
|
edges.push_back({
|
|
{"from", node_id(indices.at(relationship.parent.get()))},
|
|
{"to", node_id(indices.at(relationship.child.get()))},
|
|
{"kind", kind}
|
|
});
|
|
}
|
|
}
|
|
|
|
static std::string topology_id(const Json& topology) {
|
|
std::uint64_t hash = 1469598103934665603ULL;
|
|
for (const unsigned char byte : topology.dump()) {
|
|
hash ^= byte;
|
|
hash *= 1099511628211ULL;
|
|
}
|
|
std::array<char, 17> text{};
|
|
const auto result = std::to_chars(text.data(), text.data() + text.size() - 1,
|
|
hash, 16);
|
|
return {text.data(), result.ptr};
|
|
}
|
|
|
|
static Json renderable_task_graph(Renderable_Base& renderable) {
|
|
const auto graph = renderable.task_graph();
|
|
Json nodes = Json::array();
|
|
Json edges = Json::array();
|
|
for (std::size_t index = 0; index < graph->nodes().size(); ++index) {
|
|
const auto& node = graph->nodes()[index];
|
|
nodes.push_back({
|
|
{"id", "task-" + std::to_string(index)},
|
|
{"label", node.name.empty() ? "绘制任务 " + std::to_string(index + 1)
|
|
: std::string(node.name)},
|
|
{"kind", "task"}
|
|
});
|
|
for (const std::size_t successor : node.successors) {
|
|
edges.push_back({
|
|
{"from", "task-" + std::to_string(index)},
|
|
{"to", "task-" + std::to_string(successor)},
|
|
{"kind", "dependency"}
|
|
});
|
|
}
|
|
}
|
|
Json result{{"nodes", std::move(nodes)}, {"edges", std::move(edges)}};
|
|
result["topology_id"] = topology_id(result);
|
|
return result;
|
|
}
|
|
|
|
std::vector<Entry> entries_;
|
|
};
|
|
|
|
} // namespace renderive::web
|