asd
This commit is contained in:
parent
8232b4d3f3
commit
f5ae66e82d
7 changed files with 191 additions and 140 deletions
|
@ -14,8 +14,8 @@
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
u16 window_width = 2200;
|
u16 window_width = 1920;
|
||||||
u16 window_height = 1200;
|
u16 window_height = 1080;
|
||||||
|
|
||||||
u64 init_time, last_time, current_time, delta_t;
|
u64 init_time, last_time, current_time, delta_t;
|
||||||
u32 frame;
|
u32 frame;
|
||||||
|
|
|
@ -23,4 +23,126 @@ namespace K::PlugboardGraph {
|
||||||
static T_Map<T_Count>::type table[] = {f32{}, i32{}, RGBA{}, XY{}, XYZ{}, String{}};
|
static T_Map<T_Count>::type table[] = {f32{}, i32{}, RGBA{}, XY{}, XYZ{}, String{}};
|
||||||
return table[i];
|
return table[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T_Map<T_Count>::type Eval(const CompState& s, const ConnectInfo& info) {
|
||||||
|
Vector<T_Map<T_Count>::type> pack;
|
||||||
|
for (u32 i = 0; i < info.p->inputs_fed.size(); i++) {
|
||||||
|
bool good;
|
||||||
|
auto val = ConvertValue(std::visit([&s](auto&& arg) {
|
||||||
|
using T = std::decay_t<decltype(arg)>;
|
||||||
|
if constexpr (std::is_same_v<T, ConnectInfo>)
|
||||||
|
return Eval(s,arg);
|
||||||
|
else if constexpr (std::is_same_v<T, T_Map<Type::T_Count>::type>)
|
||||||
|
return arg;
|
||||||
|
}, info.p->inputs_fed[i]), info.p->node->in[i].type, good);
|
||||||
|
if (good) {
|
||||||
|
pack.push_back(val);
|
||||||
|
}
|
||||||
|
else return {}; // todo panic!
|
||||||
|
}
|
||||||
|
return info.p->node->fetch[info.index](s, pack);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeInstance MakeInstance(Node& n) {
|
||||||
|
Vector<std::variant<T_Map<Type::T_Count>::type, ConnectInfo>> inp;
|
||||||
|
Vector<Vector<ConnectInfo>> out;
|
||||||
|
for (const auto& t : n.in)
|
||||||
|
inp.emplace_back(expand_type(t.type));
|
||||||
|
for (auto t : n.out)
|
||||||
|
out.emplace_back();
|
||||||
|
return {
|
||||||
|
.node = &n,
|
||||||
|
.inputs_fed = std::move(inp),
|
||||||
|
.outputs_going = std::move(out)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
T_Map<T_Count>::type ConvertValue(const T_Map<T_Count>::type& v, Type target, bool& good) {
|
||||||
|
good = true;
|
||||||
|
return std::visit([&good, target](auto&& arg) -> T_Map<T_Count>::type {
|
||||||
|
using T = std::decay_t<decltype(arg)>;
|
||||||
|
if constexpr (std::is_same_v<T, T_Map<T_Float>::type>) {
|
||||||
|
switch (target) {
|
||||||
|
case T_Float:
|
||||||
|
return arg;
|
||||||
|
case T_Int:
|
||||||
|
return static_cast<T_Map<T_Float>::type>(arg);
|
||||||
|
case T_RGBA:
|
||||||
|
return RGBA{ arg, arg, arg, arg };
|
||||||
|
case T_XY:
|
||||||
|
return XY{ arg, arg };
|
||||||
|
case T_XYZ:
|
||||||
|
return XYZ{ arg, arg, arg };
|
||||||
|
case T_String:
|
||||||
|
return VarToString(arg);
|
||||||
|
case T_Count:
|
||||||
|
good = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, T_Map<T_Int>::type>) {
|
||||||
|
auto f = static_cast<T_Map<T_Float>::type>(arg);
|
||||||
|
switch (target) {
|
||||||
|
case T_Float:
|
||||||
|
return f;
|
||||||
|
case T_Int:
|
||||||
|
return arg;
|
||||||
|
case T_RGBA:
|
||||||
|
return RGBA{ f, f, f, f };
|
||||||
|
case T_XY:
|
||||||
|
return XY{ f, f };
|
||||||
|
case T_XYZ:
|
||||||
|
return XYZ{ f, f, f };
|
||||||
|
case T_String:
|
||||||
|
return VarToString(arg);
|
||||||
|
case T_Count:
|
||||||
|
good = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, T_Map<T_RGBA>::type>) {
|
||||||
|
switch (target) {
|
||||||
|
case T_RGBA:
|
||||||
|
return arg;
|
||||||
|
case T_String:
|
||||||
|
return VarToString(arg);
|
||||||
|
default:
|
||||||
|
good = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, T_Map<T_XY>::type>) {
|
||||||
|
switch (target) {
|
||||||
|
case T_XY:
|
||||||
|
return arg;
|
||||||
|
case T_String:
|
||||||
|
return VarToString(arg);
|
||||||
|
default:
|
||||||
|
good = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, T_Map<T_XYZ>::type>) {
|
||||||
|
switch (target) {
|
||||||
|
case T_XYZ:
|
||||||
|
return arg;
|
||||||
|
case T_String:
|
||||||
|
return VarToString(arg);
|
||||||
|
default:
|
||||||
|
good = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, T_Map<T_String>::type>) {
|
||||||
|
switch (target) {
|
||||||
|
case T_String:
|
||||||
|
return arg;
|
||||||
|
default:
|
||||||
|
good = false;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}, v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,9 @@ namespace K::ShaderGraph {
|
||||||
if (RGBA_node->node->out_types[RGBA_node_out_index] != Type::T_RGBA) return {};
|
if (RGBA_node->node->out_types[RGBA_node_out_index] != Type::T_RGBA) return {};
|
||||||
return BuildNode(*RGBA_node);
|
return BuildNode(*RGBA_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T_Map<T_Count>::type expand_type(Type i) {
|
||||||
|
static constexpr T_Map<T_Count>::type table[] = {f32{}, i32{}, RGBA{}, XY{}, XYZ{}};
|
||||||
|
return table[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@ namespace {
|
||||||
draw_comp = true,
|
draw_comp = true,
|
||||||
draw_interpolation = true,
|
draw_interpolation = true,
|
||||||
draw_properties = true,
|
draw_properties = true,
|
||||||
draw_assets = true,
|
draw_assets = true;
|
||||||
draw_color = true;
|
|
||||||
|
|
||||||
const f32 row_height = 20.0f;
|
const f32 row_height = 20.0f;
|
||||||
|
|
||||||
|
@ -796,7 +795,7 @@ namespace K::UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(nodes_begin);
|
ImGui::SetCursorScreenPos(nodes_begin);
|
||||||
if (ImGui::BeginChild("Overlay", {}, false, ImGuiWindowFlags_NoInputs)) {
|
if (ImGui::BeginChild("Overlay", {table_left - ImGui::GetWindowPos().x, 0.0f}, false, ImGuiWindowFlags_NoInputs)) {
|
||||||
for (auto &[_, link]: s.plugboard.links_pos)
|
for (auto &[_, link]: s.plugboard.links_pos)
|
||||||
for (auto &sink: link.sinks)
|
for (auto &sink: link.sinks)
|
||||||
ImGui::GetCurrentWindow()->DrawList->AddBezierCubic(link.source,
|
ImGui::GetCurrentWindow()->DrawList->AddBezierCubic(link.source,
|
||||||
|
@ -827,8 +826,9 @@ namespace K::UI {
|
||||||
void Shader(CompState& s) {
|
void Shader(CompState& s) {
|
||||||
if (ImGui::Begin("Shader", &draw_shader)) {
|
if (ImGui::Begin("Shader", &draw_shader)) {
|
||||||
if (s.active == -1)
|
if (s.active == -1)
|
||||||
ImGui::Text("No active layer.");
|
ImGui::TextUnformatted("No active layer.");
|
||||||
else {
|
else {
|
||||||
|
ImGui::Text("Editing Layer %u: %s", s.active, s.layers[s.active].name.c_str());
|
||||||
static i32 type_current = 0;
|
static i32 type_current = 0;
|
||||||
static String name{};
|
static String name{};
|
||||||
if (ImGui::Button("Add Uniform") && !name.empty() && !isdigit(name[0]) && !(name[0] == 'f' && name.length() == 1)) {
|
if (ImGui::Button("Add Uniform") && !name.empty() && !isdigit(name[0]) && !(name[0] == 'f' && name.length() == 1)) {
|
||||||
|
@ -892,7 +892,6 @@ namespace K::UI {
|
||||||
}
|
}
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
ImGui::Text("Editing Layer %u: %s", s.active, s.layers[s.active].name.c_str());
|
|
||||||
ImGui::InputTextMultiline("##source", &s.layers[s.active].track.shader, ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y - 25.0f), ImGuiInputTextFlags_AllowTabInput);
|
ImGui::InputTextMultiline("##source", &s.layers[s.active].track.shader, ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y - 25.0f), ImGuiInputTextFlags_AllowTabInput);
|
||||||
if (ImGui::Button("Submit Shader"))
|
if (ImGui::Button("Submit Shader"))
|
||||||
s.layers[s.active].track.compile();
|
s.layers[s.active].track.compile();
|
||||||
|
@ -902,8 +901,60 @@ namespace K::UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpolation(CompState& s) {
|
void Interpolation(CompState& s) {
|
||||||
if (ImGui::Begin("Interpolation", &draw_interpolation)) {
|
if (ImGui::Begin("Interpolation", &draw_interpolation, ImGuiWindowFlags_NoScrollbar)) {
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
static const ImVec2 p1 { 0.0f, 0.0f}, p4 {1.0f, 1.0f};
|
||||||
|
static ImVec2 p2 { 1.0f, 0.0f }, p3 { 0.0f, 1.0f }, p2_old = p2, p3_old = p3;
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {10.0f, 10.0f});
|
||||||
|
f32 y_max = std::max(std::max(p2.y, p3.y), p4.y),
|
||||||
|
y_min = std::min(std::min(p1.y, p2.y), p3.y),
|
||||||
|
y_range = y_max - y_min;
|
||||||
|
|
||||||
|
f32 w_window = ImGui::GetContentRegionAvail().x;
|
||||||
|
if (ImGui::BeginChild("Canvas", {w_window, w_window}, ImGuiChildFlags_Border, ImGuiWindowFlags_NoScrollbar)) {
|
||||||
|
f32 w = ImGui::GetContentRegionAvail().x;
|
||||||
|
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||||
|
auto *dl = ImGui::GetWindowDrawList();
|
||||||
|
ImVec2 tp1 = pos + ImVec2{p1.x * w, ((1.0f - p1.y) - (1.0f - y_max)) * w / y_range},
|
||||||
|
tp4 = pos + ImVec2{p4.x * w, ((1.0f - p4.y) - (1.0f - y_max)) * w / y_range},
|
||||||
|
tp2 = pos + ImVec2{p2.x * w, ((1.0f - p2.y) - (1.0f - y_max)) * w / y_range},
|
||||||
|
tp3 = pos + ImVec2{p3.x * w, ((1.0f - p3.y) - (1.0f - y_max)) * w / y_range};
|
||||||
|
dl->AddBezierCubic(tp1, tp2, tp3, tp4, 0xFFFFFFFF, 2.0f);
|
||||||
|
dl->AddLine(tp1, tp2, 0xAAFFFFFF, 1.5f);
|
||||||
|
dl->AddLine(tp3, tp4, 0xAAFFFFFF, 1.5f);
|
||||||
|
dl->AddLine(tp1, {tp1.x + w, tp1.y}, 0x44FFFFFF, 2.0f);
|
||||||
|
dl->AddLine(tp4, {tp4.x - w, tp4.y}, 0x44FFFFFF, 2.0f);
|
||||||
|
dl->AddText(tp1 + ImVec2{10.0f, -20.0f}, 0xFFFFFFFF, "0.0");
|
||||||
|
dl->AddText(tp4 + ImVec2{-30.0f, 15.0f}, 0xFFFFFFFF, "1.0");
|
||||||
|
|
||||||
|
ImVec2 size = {15.0f, 15.0f};
|
||||||
|
ImGui::SetCursorScreenPos(tp2 - size / 2.0f);
|
||||||
|
ImVec2 off = io.MousePos - io.MouseClickedPos[0];
|
||||||
|
ImGui::Button("##P2", size);
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
p2_old = p2;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemActive()) {
|
||||||
|
p2 = p2_old + ImVec2{off.x / w, off.y / -w * y_range};
|
||||||
|
p2.x = std::clamp(p2.x, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetCursorScreenPos(tp3 - size / 2.0f);
|
||||||
|
ImGui::Button("##P3", size);
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
p3_old = p3;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemActive()) {
|
||||||
|
p3 = p3_old + ImVec2{off.x / w, off.y / -w * y_range};
|
||||||
|
p3.x = std::clamp(p3.x, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::Button("Apply");
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
@ -922,13 +973,6 @@ namespace K::UI {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Color(CompState& s) {
|
|
||||||
if (ImGui::Begin("Color", &draw_color)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Draw(u32 frame, CompState& s) {
|
void Draw(u32 frame, CompState& s) {
|
||||||
ImGui_ImplSDL2_NewFrame();
|
ImGui_ImplSDL2_NewFrame();
|
||||||
ImGui_Implbgfx_NewFrame();
|
ImGui_Implbgfx_NewFrame();
|
||||||
|
@ -946,7 +990,6 @@ namespace K::UI {
|
||||||
if (draw_interpolation) Interpolation(s);
|
if (draw_interpolation) Interpolation(s);
|
||||||
if (draw_properties) Properties(s);
|
if (draw_properties) Properties(s);
|
||||||
if (draw_assets) Assets(s);
|
if (draw_assets) Assets(s);
|
||||||
if (draw_color) Color(s);
|
|
||||||
|
|
||||||
if (save_called && ready_frame == frame) {
|
if (save_called && ready_frame == frame) {
|
||||||
stbi_write_png("frame.png", static_cast<i32>(s.width), static_cast<i32>(s.height), 4, save_buffer, static_cast<i32>(s.width) * 4);
|
stbi_write_png("frame.png", static_cast<i32>(s.width), static_cast<i32>(s.height), 4, save_buffer, static_cast<i32>(s.width) * 4);
|
||||||
|
|
|
@ -114,7 +114,7 @@ namespace K::Graphics {
|
||||||
|
|
||||||
"Hue",
|
"Hue",
|
||||||
"Saturation",
|
"Saturation",
|
||||||
"Color",
|
"Colour",
|
||||||
"Luminosity",
|
"Luminosity",
|
||||||
|
|
||||||
"Invalid"
|
"Invalid"
|
||||||
|
|
|
@ -118,127 +118,11 @@ namespace K::PlugboardGraph {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
inline NodeInstance MakeInstance(Node& n) {
|
NodeInstance MakeInstance(Node& n);
|
||||||
Vector<std::variant<T_Map<Type::T_Count>::type, ConnectInfo>> inp;
|
|
||||||
Vector<Vector<ConnectInfo>> out;
|
|
||||||
for (const auto& t : n.in)
|
|
||||||
inp.emplace_back(expand_type(t.type));
|
|
||||||
for (auto t : n.out)
|
|
||||||
out.emplace_back();
|
|
||||||
return {
|
|
||||||
.node = &n,
|
|
||||||
.inputs_fed = std::move(inp),
|
|
||||||
.outputs_going = std::move(out)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline T_Map<T_Count>::type ConvertValue(const T_Map<T_Count>::type& v, Type target, bool& good) {
|
T_Map<T_Count>::type ConvertValue(const T_Map<T_Count>::type& v, Type target, bool& good);
|
||||||
good = true;
|
|
||||||
return std::visit([&good, target](auto&& arg) -> T_Map<T_Count>::type {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_Float>::type>) {
|
|
||||||
switch (target) {
|
|
||||||
case T_Float:
|
|
||||||
return arg;
|
|
||||||
case T_Int:
|
|
||||||
return static_cast<T_Map<T_Float>::type>(arg);
|
|
||||||
case T_RGBA:
|
|
||||||
return RGBA{ arg, arg, arg, arg };
|
|
||||||
case T_XY:
|
|
||||||
return XY{ arg, arg };
|
|
||||||
case T_XYZ:
|
|
||||||
return XYZ{ arg, arg, arg };
|
|
||||||
case T_String:
|
|
||||||
return VarToString(arg);
|
|
||||||
case T_Count:
|
|
||||||
good = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_Int>::type>) {
|
|
||||||
auto f = static_cast<T_Map<T_Float>::type>(arg);
|
|
||||||
switch (target) {
|
|
||||||
case T_Float:
|
|
||||||
return f;
|
|
||||||
case T_Int:
|
|
||||||
return arg;
|
|
||||||
case T_RGBA:
|
|
||||||
return RGBA{ f, f, f, f };
|
|
||||||
case T_XY:
|
|
||||||
return XY{ f, f };
|
|
||||||
case T_XYZ:
|
|
||||||
return XYZ{ f, f, f };
|
|
||||||
case T_String:
|
|
||||||
return VarToString(arg);
|
|
||||||
case T_Count:
|
|
||||||
good = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_RGBA>::type>) {
|
|
||||||
switch (target) {
|
|
||||||
case T_RGBA:
|
|
||||||
return arg;
|
|
||||||
case T_String:
|
|
||||||
return VarToString(arg);
|
|
||||||
default:
|
|
||||||
good = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_XY>::type>) {
|
|
||||||
switch (target) {
|
|
||||||
case T_XY:
|
|
||||||
return arg;
|
|
||||||
case T_String:
|
|
||||||
return VarToString(arg);
|
|
||||||
default:
|
|
||||||
good = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_XYZ>::type>) {
|
|
||||||
switch (target) {
|
|
||||||
case T_XYZ:
|
|
||||||
return arg;
|
|
||||||
case T_String:
|
|
||||||
return VarToString(arg);
|
|
||||||
default:
|
|
||||||
good = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, PlugboardGraph::T_Map<PlugboardGraph::T_String>::type>) {
|
|
||||||
switch (target) {
|
|
||||||
case T_String:
|
|
||||||
return arg;
|
|
||||||
default:
|
|
||||||
good = false;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline T_Map<T_Count>::type Eval(const CompState& s, const ConnectInfo& info) {
|
T_Map<T_Count>::type Eval(const CompState& s, const ConnectInfo& info);
|
||||||
Vector<T_Map<T_Count>::type> pack;
|
|
||||||
for (u32 i = 0; i < info.p->inputs_fed.size(); i++) {
|
|
||||||
bool good;
|
|
||||||
auto val = ConvertValue(std::visit([&s](auto&& arg) {
|
|
||||||
using T = std::decay_t<decltype(arg)>;
|
|
||||||
if constexpr (std::is_same_v<T, ConnectInfo>)
|
|
||||||
return Eval(s,arg);
|
|
||||||
else if constexpr (std::is_same_v<T, T_Map<Type::T_Count>::type>)
|
|
||||||
return arg;
|
|
||||||
}, info.p->inputs_fed[i]), info.p->node->in[i].type, good);
|
|
||||||
if (good) {
|
|
||||||
pack.push_back(val);
|
|
||||||
}
|
|
||||||
else return {}; // todo panic!
|
|
||||||
}
|
|
||||||
return info.p->node->fetch[info.index](s, pack);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LinksFromSource {
|
struct LinksFromSource {
|
||||||
ImVec2 source;
|
ImVec2 source;
|
||||||
|
|
|
@ -46,10 +46,7 @@ namespace K::ShaderGraph {
|
||||||
};
|
};
|
||||||
String VarToString(const T_Map<T_Count>::type& var);
|
String VarToString(const T_Map<T_Count>::type& var);
|
||||||
|
|
||||||
inline T_Map<T_Count>::type expand_type(Type i) {
|
T_Map<T_Count>::type expand_type(Type i);
|
||||||
static constexpr T_Map<T_Count>::type table[] = {f32{}, i32{}, ShaderGraph::RGBA{}, ShaderGraph::XY{}, ShaderGraph::XYZ{}};
|
|
||||||
return table[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Node;
|
struct Node;
|
||||||
struct InSlot {
|
struct InSlot {
|
||||||
|
|
Loading…
Add table
Reference in a new issue