#include "../graph/graph_internal.h" #include "project_internal.h" #include #include 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; }