Move to EBML-based project file format

This commit is contained in:
mid
2025-10-05 23:21:21 +03:00
parent 33c9207947
commit b6e6713860
10 changed files with 768 additions and 531 deletions

View File

@@ -22,13 +22,26 @@
#include"timeline.h"
#include<functional>
#include<algorithm>
#include<eebie/writer.h>
#define SSE_MATHFUN_WITH_CODE
#include<hi/kumb.h>
#include<hi/linearity.h>
static Frame *globaldis;
extern Frame *globaldis;
// The Preview node is actually a NOOP
// Must inject our own function when either loading or creating projects
static int INJECTED_PREVIEW_FUNC(CHiPubNode *preview) {
CHiValue *val = CHi_Crawl(&preview->sinks[0]);
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
globaldis->viewer->SetImage(val->data.sample);
}
return 1;
};
std::string node_name_from_id(uint64_t id) {
static std::unordered_map<uint64_t, std::string> NODE_ID_NAMES = {
@@ -224,76 +237,40 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT};
if(save.ShowModal() == wxID_OK) {
FILE *f = fopen(save.GetPath().mb_str(), "wb");
struct UD {
FILE *f;
Frame *frame;
} ud;
CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){
auto f = (FILE*) ud;
return fwrite(data, 1, len, f);
}, f);
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]);
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
fwrite(pos, sizeof(pos), 1, f);
wxString path = save.GetPath();
if(!path.EndsWith(".ctc")) {
path += ".ctc";
}
fclose(f);
ud.f = fopen(path.mb_str(), "wb");
ud.frame = this;
CHi_NodeGraphSave(graph->backendNG, +[](void *udPtr, const void *data, size_t len){
auto &ud = *(UD*) udPtr;
return fwrite(data, 1, len, ud.f);
}, +[](void *udPtr, EBMLWriter *ebml, CHiPubNode *node){
auto &ud = *(UD*) udPtr;
GrNode *gn = ud.frame->graph->get_graphical(node);
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
ebml_writer_push(ebml, 0x5000);
ebml_writer_put(ebml, 0x5001, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = (uint8_t*) &pos, sizeof(pos)}});
ebml_writer_pop(ebml);
}, &ud);
fclose(ud.f);
}
} else if(ev.GetId() == wxID_OPEN) {
wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST};
if(load.ShowModal() == wxID_OK) {
FILE *f = fopen(load.GetPath().mb_str(), "rb");
if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){
auto f = (FILE*) ud;
return fread(data, 1, len, f);
}, f) == 0) {
for(GrNode *gnode : graph->gnodes) {
gnode->Destroy();
}
graph->gnodes.clear();
graph->links.clear();
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gnode = new GrNode(graph);
int32_t pos[2];
fread(pos, sizeof(pos), 1, f);
gnode->SetPosition({pos[0], pos[1]});
gnode->logical = graph->backendNG->nodes[n];
ShapeGrNode(gnode);
graph->gnodes.push_back(gnode);
}
for(size_t n = 0; n < graph->backendNG->count; n++) {
CHiPubNode *node = graph->backendNG->nodes[n];
for(size_t s = 0; s < node->sinkCount; s++) {
if(node->sinks[s].type == CUTIHI_VAL_LINKED) {
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx});
}
}
}
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
if(graph->backendNG->duration <= 0) {
toolbar.durationEnable->SetValue(false);
toolbar.duration->Enable(false);
}
}
fclose(f);
LoadProject(load.GetPath().utf8_string());
}
}
});
@@ -347,27 +324,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
tlba->AddControl(toolbar.duration);
tlba->AddControl(toolbar.btnPerform);
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
wxTheApp->CallAfter([ng](){
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
btn->Enable();
btn->SetLabel("Compile");
auto tlba = ((Frame*) ng->ud)->tlba;
tlba->EnableTool(wxID_SAVE, true);
tlba->EnableTool(wxID_OPEN, true);
});
};
graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) {
wxTheApp->CallAfter([ng, n](){
auto frame = (Frame*) ng->ud;
GrNode *gn = frame->graph->get_graphical(n);
gn->Refresh();
});
};
tlba->Realize();
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
@@ -375,6 +331,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem
Centre();
LoadProject("");
graph->CreatePreviewNode();
}
@@ -382,6 +339,101 @@ Frame::~Frame() {
aui.UnInit();
}
void Frame::LoadProject(std::string filename) {
struct UD {
FILE *f;
Frame *frame;
std::unordered_map<uint64_t, wxPoint> nodePositions;
} ud = {};
if(filename != "") {
ud.f = fopen(filename.c_str(), "rb");
}
ud.frame = this;
if(filename == "" || CHi_NodeGraphLoad(graph->backendNG, +[](void *udPtr, void *data, size_t len){
auto &ud = *(UD*) udPtr;
return fread(data, 1, len, ud.f);
}, +[](void *udPtr, uint64_t nodeIdx, uint64_t elId, const void *data, size_t len){
auto &ud = *(UD*) udPtr;
if(elId == 0x5001) {
auto pos = (const int32_t*) data;
ud.nodePositions.emplace(nodeIdx, wxPoint{pos[0], pos[1]});
}
}, &ud) == 0) {
for(GrNode *gnode : graph->gnodes) {
gnode->Destroy();
}
graph->gnodes.clear();
graph->links.clear();
for(size_t n = 0; n < graph->backendNG->count; n++) {
GrNode *gnode = new GrNode(graph);
gnode->SetPosition(ud.nodePositions[n]);
gnode->logical = graph->backendNG->nodes[n];
ShapeGrNode(gnode);
if(gnode->logical->type == CUTIHI_T('CPre', 'view')) {
gnode->logical->Perform = INJECTED_PREVIEW_FUNC;
}
graph->gnodes.push_back(gnode);
}
for(size_t n = 0; n < graph->backendNG->count; n++) {
CHiPubNode *node = graph->backendNG->nodes[n];
for(size_t s = 0; s < node->sinkCount; s++) {
if(node->sinks[s].linked.to) {
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].linked.to; }), node->sinks[s].linked.idx});
}
}
}
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
if(graph->backendNG->duration <= 0) {
toolbar.durationEnable->SetValue(false);
toolbar.duration->Enable(false);
}
timeline->ResetRows();
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
wxTheApp->CallAfter([ng](){
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
btn->Enable();
btn->SetLabel("Compile");
auto tlba = ((Frame*) ng->ud)->tlba;
tlba->EnableTool(wxID_SAVE, true);
tlba->EnableTool(wxID_OPEN, true);
});
};
graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) {
wxTheApp->CallAfter([ng, n](){
auto frame = (Frame*) ng->ud;
GrNode *gn = frame->graph->get_graphical(n);
gn->Refresh();
});
};
}
if(ud.f) {
fclose(ud.f);
}
// Force everything in the graph to be computed so the preview immediately shows up
CHi_Hysteresis(graph->backendNG);
}
bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false;
@@ -400,7 +452,7 @@ void GrNode::MakeKeyframe(int sinkIdx) {
CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx);
((Frame*) ng->GetParent())->timeline->Refresh();
globaldis->timeline->ResetRows();
}
static bool has_errors(CHiPubNode *pn) {
@@ -1048,7 +1100,7 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
after();
((Frame*) GetParent())->timeline->ResetRows();
globaldis->timeline->ResetRows();
}
pthread_mutex_unlock(&backendNG->mut);
@@ -1114,17 +1166,16 @@ NodeGraph::~NodeGraph() {
void NodeGraph::Alinken(Link l) {
pthread_mutex_lock(&backendNG->mut);
CHiValue newv;
newv.type = CUTIHI_VAL_LINKED;
newv.data.linked.to = l.output->logical;
newv.data.linked.idx = l.o;
CHiValue newv = {};
newv.linked.to = l.output->logical;
newv.linked.idx = l.o;
int err = CHi_ConfigureSink(l.input->logical, l.i, newv);
pthread_mutex_unlock(&backendNG->mut);
if(!err) {
((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
globaldis->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
return;
}
@@ -1161,7 +1212,7 @@ void NodeGraph::Dirtify(GrNode *g) {
}
}*/
CHi_Hysteresis(gnodes[0]->logical);
CHi_Hysteresis(backendNG);
}
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
@@ -1197,17 +1248,23 @@ void NodeGraph::CreatePreviewNode() {
v->SetPosition(wxPoint{GetSize().x - v->GetSize().x - 8, 8});
// The Preview node is actually a NOOP
// Must inject our own function in Perform
v->logical->Perform = +[](CHiPubNode *preview){
CHiValue *val = CHi_Crawl(&preview->sinks[0]);
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
globaldis->viewer->SetImage(val->data.sample);
v->logical->Perform = INJECTED_PREVIEW_FUNC;
}
void NodeGraph::DestroyNode(GrNode *gn) {
for(auto it = links.begin(); it != links.end();) {
if(it->input == gn || it->output == gn) {
it = links.erase(it);
} else {
it++;
}
return 1;
};
}
gnodes.erase(std::find(gnodes.begin(), gnodes.end(), gn));
wxTheApp->CallAfter([=](){
gn->Destroy();
});
}
GrNode *NodeGraph::get_graphical(CHiPubNode *n) {

View File

@@ -43,6 +43,8 @@ struct Frame : wxFrame {
Frame();
virtual ~Frame();
void LoadProject(std::string filename);
};
struct GrNode : wxPanel {
@@ -136,6 +138,8 @@ struct NodeGraph : wxPanel {
void CreatePreviewNode();
void DestroyNode(GrNode*);
GrNode *get_graphical(CHiPubNode*);
};

View File

@@ -2,6 +2,8 @@
#include"frame.h"
Frame *globaldis;
struct App : wxApp {
virtual bool OnInit() {
(new Frame())->Show(true);
@@ -9,4 +11,4 @@ struct App : wxApp {
}
};
wxIMPLEMENT_APP(App);
wxIMPLEMENT_APP(App);

View File

@@ -10,6 +10,8 @@
#include"frame.h"
extern Frame *globaldis;
static wxBitmap bmpKf;
static wxBitmap bmpKfExtrap;
@@ -38,7 +40,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) {
return false;
}
auto f = (Frame*) GetParent();
auto f = globaldis;
kfs = row->kfs;
@@ -59,7 +61,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) {
void Timeline::ResetRows() {
rows.clear();
auto frame = (Frame*) GetParent();
auto frame = globaldis;
auto kfsList = &frame->graph->backendNG->keyframesList;
@@ -92,13 +94,15 @@ void Timeline::ResetRows() {
y += row.h;
}
Refresh();
}
float Timeline::SnapTime(float t) {
float minDist = FLT_MAX;
float newT = t;
auto f = (Frame*) GetParent();
auto f = globaldis;
std::vector<float> times;
for(Timeline::Row &row : rows) {
@@ -149,7 +153,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){
Timeline::Row *row = GetRow(ev.GetPosition().y);
auto f = (Frame*) GetParent();
auto f = globaldis;
float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
if(ev.ControlDown()) {
@@ -196,14 +200,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
t = SnapTime(t);
}
row->gn->logical->lifespan.end = t;
row->gn->logical->lifespan.end = t < 0 ? 0 : t;
Refresh();
}
});
Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){
auto f = (Frame*) GetParent();
auto f = globaldis;
if(HasCapture()) {
if(captureMode == Timeline::CaptureMode::CAM) {
@@ -319,7 +323,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
if(ev.GetId() == idDel) {
auto f = (Frame*) GetParent();
auto f = globaldis;
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
@@ -337,7 +341,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
}
void Timeline::Paint(wxPaintEvent &ev) {
auto frame = (Frame*) GetParent();
auto frame = globaldis;
wxPaintDC dc{this};
@@ -405,6 +409,10 @@ void Timeline::Paint(wxPaintEvent &ev) {
if(end == 0) {
end = gn->logical->ng->duration;
if(end == -1) {
// Hack :)
end = 3600 * 10;
}
}
start *= scale;