203 lines
6.1 KiB
C
203 lines
6.1 KiB
C
#include "../graph/graph_internal.h"
|
|
#include "project_internal.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
struct xtc_history {
|
|
xtc_project *states[XTC_HISTORY_EDITS + 1];
|
|
size_t count, cursor;
|
|
xtc_project *reserved;
|
|
uint64_t next_track, next_clip, next_note;
|
|
size_t next_node;
|
|
};
|
|
|
|
/**
|
|
* @brief Raises history's identifier high-water marks to include the project's allocations.
|
|
*
|
|
* @param h Snapshot history handle.
|
|
* @param p Project model; borrowed for this call.
|
|
*/
|
|
static void remember_ids(xtc_history *h, const xtc_project *p)
|
|
{
|
|
if (p->next_track_id > h->next_track)
|
|
h->next_track = p->next_track_id;
|
|
if (p->next_clip_id > h->next_clip)
|
|
h->next_clip = p->next_clip_id;
|
|
if (p->next_note_id > h->next_note)
|
|
h->next_note = p->next_note_id;
|
|
if (p->graph->next_id > h->next_node)
|
|
h->next_node = p->graph->next_id;
|
|
}
|
|
|
|
/**
|
|
* @brief Releases all retained snapshots and history storage; accepts NULL.
|
|
*
|
|
* @param h Snapshot history handle.
|
|
*/
|
|
void xtc_history_destroy(xtc_history *h)
|
|
{
|
|
size_t i;
|
|
if (!h)
|
|
return;
|
|
for (i = 0; i < h->count; ++i)
|
|
xtc_project_destroy(h->states[i]);
|
|
xtc_project_destroy(h->reserved);
|
|
free(h);
|
|
}
|
|
|
|
/**
|
|
* @brief Creates snapshot history with an independent copy of the initial project.
|
|
*
|
|
* @param p Project model; borrowed for this call.
|
|
* @param out Receives the newly allocated object; caller owns it on success.
|
|
* @return XTC_OK on success, or an error code describing the failure.
|
|
*/
|
|
xtc_result xtc_history_create(const xtc_project *p, xtc_history **out)
|
|
{
|
|
xtc_history *h;
|
|
xtc_result r;
|
|
if (!out)
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
*out = NULL;
|
|
if (!p)
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
h = calloc(1, sizeof(*h));
|
|
if (!h)
|
|
return XTC_ERR_OUT_OF_MEMORY;
|
|
r = xtc_project_clone(p, h->states);
|
|
if (r != XTC_OK) {
|
|
free(h);
|
|
return r;
|
|
}
|
|
remember_ids(h, p);
|
|
h->count = 1;
|
|
*out = h;
|
|
return XTC_OK;
|
|
}
|
|
|
|
xtc_result xtc_history_reserve(xtc_history *h)
|
|
{
|
|
if (!h) return XTC_ERR_INVALID_ARGUMENT;
|
|
if (h->reserved) return XTC_OK;
|
|
return xtc_project_create(&h->reserved);
|
|
}
|
|
xtc_result xtc_history_counts(const xtc_history *h, size_t *undo_count, size_t *redo_count)
|
|
{
|
|
if (!h || !undo_count || !redo_count || undo_count == redo_count) return XTC_ERR_INVALID_ARGUMENT;
|
|
*undo_count = h->cursor;
|
|
*redo_count = h->count-h->cursor-1;
|
|
return XTC_OK;
|
|
}
|
|
/* Copy into the preallocated project and 64-node/4096-edge graph. No allocation
|
|
* can fail after an accepted model command or successful engine publication. */
|
|
static xtc_result capture(xtc_history *h, const xtc_project *p, xtc_project **out)
|
|
{
|
|
xtc_project *copy = h->reserved;
|
|
xtc_graph *graph;
|
|
xtc_node *nodes;
|
|
xtc_edge *edges;
|
|
if (!copy) return xtc_project_clone(p,out);
|
|
if (p->graph->node_count > 64 || p->graph->edge_count > 4096) return XTC_ERR_CAPACITY;
|
|
graph = copy->graph; nodes = graph->nodes; edges = graph->edges;
|
|
*copy = *p;
|
|
copy->graph = graph;
|
|
*graph = *p->graph;
|
|
graph->max_nodes = 64; graph->nodes = nodes; graph->edges = edges;
|
|
memcpy(nodes,p->graph->nodes,p->graph->node_count*sizeof(*nodes));
|
|
memcpy(edges,p->graph->edges,p->graph->edge_count*sizeof(*edges));
|
|
h->reserved = NULL;
|
|
*out = copy;
|
|
return XTC_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Appends a project snapshot, discarding redo states and evicting the oldest state if full.
|
|
*
|
|
* @param h Snapshot history handle.
|
|
* @param p Project model; borrowed for this call.
|
|
* @return XTC_OK on success, or an error code describing the failure.
|
|
*/
|
|
xtc_result xtc_history_record(xtc_history *h, const xtc_project *p)
|
|
{
|
|
xtc_project *copy;
|
|
size_t i;
|
|
xtc_result r;
|
|
if (!h || !p)
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
r = capture(h,p,©);
|
|
if (r != XTC_OK)
|
|
return r;
|
|
remember_ids(h, p);
|
|
for (i = h->cursor + 1; i < h->count; ++i)
|
|
xtc_project_destroy(h->states[i]);
|
|
h->count = h->cursor + 1;
|
|
if (h->count == XTC_HISTORY_EDITS + 1) {
|
|
xtc_project_destroy(h->states[0]);
|
|
memmove(h->states, h->states + 1, XTC_HISTORY_EDITS * sizeof(*h->states));
|
|
--h->count;
|
|
}
|
|
h->states[h->count++] = copy;
|
|
h->cursor = h->count - 1;
|
|
return XTC_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks whether an adjacent undo or redo snapshot exists.
|
|
*
|
|
* @param h Snapshot history handle.
|
|
* @param redo Zero for undo, one for redo.
|
|
* @return Nonzero when the requested history state exists, zero otherwise.
|
|
*/
|
|
static int available(const xtc_history *h, int redo)
|
|
{
|
|
return redo ? h->cursor + 1 < h->count : h->cursor > 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Clones the adjacent undo or redo state without moving the history cursor.
|
|
*
|
|
* @param h Snapshot history handle.
|
|
* @param redo Zero for undo, one for redo.
|
|
* @param out Receives the newly allocated object; caller owns it on success.
|
|
* @return XTC_OK on success, or an error code describing the failure.
|
|
*/
|
|
xtc_result xtc_history_checkout(const xtc_history *h, int redo, xtc_project **out)
|
|
{
|
|
if (!out)
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
*out = NULL;
|
|
if (!h || (redo != 0 && redo != 1))
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
if (!available(h, redo))
|
|
return XTC_ERR_NOT_FOUND;
|
|
{
|
|
xtc_result r = xtc_project_clone(h->states[redo ? h->cursor + 1 : h->cursor - 1], out);
|
|
if (r == XTC_OK) {
|
|
(*out)->next_track_id = h->next_track;
|
|
(*out)->next_clip_id = h->next_clip;
|
|
(*out)->next_note_id = h->next_note;
|
|
(*out)->graph->next_id = h->next_node;
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Moves the history cursor after the checked-out project has been installed.
|
|
*
|
|
* @param h Snapshot history handle.
|
|
* @param redo Zero for undo, one for redo.
|
|
* @return XTC_OK on success, or an error code describing the failure.
|
|
*/
|
|
xtc_result xtc_history_accept(xtc_history *h, int redo)
|
|
{
|
|
if (!h || (redo != 0 && redo != 1))
|
|
return XTC_ERR_INVALID_ARGUMENT;
|
|
if (!available(h, redo))
|
|
return XTC_ERR_NOT_FOUND;
|
|
if (redo)
|
|
++h->cursor;
|
|
else
|
|
--h->cursor;
|
|
return XTC_OK;
|
|
}
|