From 295ea1bfe57ac84b536e80b5efb1a209aabd88d3 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Fri, 7 Aug 2026 15:48:34 +0800 Subject: [PATCH] refactor(undo): switch oakundo to refcounted value handles, migrate consumers - OakUndoCommand/OakUndoStack become neutral by-value handles {ctx, addref, release, abi_version}; a handle is a shared_ptr equivalent at the ABI level, internals untouched (box owns/observes flag; containers adopt, boxes created by factories own) - oaknode command factories and undoable variants return/write value handles; timeline command classes hold value handles; task import take_command returns a value handle - tests updated everywhere; suites green: oakundo 22, oaktimeline 117, oaktask 106, oaknode 96, oakrender 44, oakcodec 18, oakcommon 193, oakaudio 36 --- include/node/folder.h | 2 +- include/node/group.h | 4 +- include/node/keyframe.h | 10 +- include/node/node.h | 26 ++-- include/task/project.h | 2 +- include/timeline/edit.h | 22 +-- include/timeline/marker.h | 8 +- include/timeline/workarea.h | 4 +- include/undo/undocommand.h | 82 +++++----- include/undo/undostack.h | 61 ++++---- src/node/c_api/folder.cpp | 20 +-- src/node/c_api/group.cpp | 12 +- src/node/c_api/keyframe.cpp | 30 ++-- src/node/c_api/node.cpp | 76 ++++----- src/node/c_api/valueconvert.h | 17 +-- src/node/tests/group_test.cpp | 12 +- src/node/tests/keyframe_test.cpp | 30 ++-- src/node/tests/node_test.cpp | 54 +++---- src/task/c_api/project.cpp | 4 +- src/task/src/project/import/import.cpp | 20 +-- src/task/src/project/import/import.h | 12 +- src/task/src/project/loadotio/loadotio.cpp | 32 ++-- src/task/tests/task_test.cpp | 14 +- src/timeline/c_api/edit.cpp | 76 +++++---- src/timeline/c_api/marker.cpp | 34 ++--- src/timeline/c_api/workarea.cpp | 22 ++- src/timeline/src/timelineundocommon.h | 29 ++-- src/timeline/src/timelineundogeneral.cpp | 40 ++--- src/timeline/src/timelineundogeneral.h | 10 +- src/timeline/src/timelineundopointer.cpp | 24 +-- src/timeline/src/timelineundopointer.h | 12 +- src/timeline/src/timelineundoripple.cpp | 6 +- src/timeline/src/timelineundoripple.h | 2 +- src/timeline/src/timelineundosplit.cpp | 8 +- src/timeline/src/timelineundosplit.h | 4 +- src/timeline/tests/timeline_test.cpp | 128 ++++++++-------- src/undo/c_api/commandhandle.h | 93 ++++++++++- src/undo/c_api/undocommand.cpp | 127 ++++++++------- src/undo/c_api/undostack.cpp | 170 ++++++++++++++------- src/undo/tests/undocommand_test.cpp | 66 ++++---- src/undo/tests/undostack_test.cpp | 116 +++++++------- 41 files changed, 837 insertions(+), 684 deletions(-) diff --git a/include/node/folder.h b/include/node/folder.h index de9d589c6..f75e48cfd 100644 --- a/include/node/folder.h +++ b/include/node/folder.h @@ -84,7 +84,7 @@ OakNodeNode *oaknode_folder_as_node(OakNodeFolder *folder); * @brief Create an undoable FolderAddChild command. Owned; free with * oakundo_command_free(). */ -OakUndoCommand *oaknode_command_create_folder_add_child( +OakUndoCommand oaknode_command_create_folder_add_child( OakNodeFolder *folder, OakNodeNode *child); /** diff --git a/include/node/group.h b/include/node/group.h index f87e50fa4..7a3cf5641 100644 --- a/include/node/group.h +++ b/include/node/group.h @@ -88,7 +88,7 @@ int oaknode_group_add_input_passthrough_undoable(OakNodeGroup *group, OakNodeNode *node, const char *input_id, int element, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Remove the passthrough for (`node`, `input_id`, `element`) @@ -137,7 +137,7 @@ int oaknode_group_set_output_passthrough(OakNodeGroup *group, * (olive::NodeGroupSetOutputPassthrough). */ int oaknode_group_set_output_passthrough_undoable( - OakNodeGroup *group, OakNodeNode *node, OakUndoCommand **out_command); + OakNodeGroup *group, OakNodeNode *node, OakUndoCommand *out_command); /** * @brief Resolve an input through group passthroughs diff --git a/include/node/keyframe.h b/include/node/keyframe.h index dde58b4fa..9362d53fe 100644 --- a/include/node/keyframe.h +++ b/include/node/keyframe.h @@ -109,7 +109,7 @@ int oaknode_keyframe_set_time(OakNodeKeyframe *keyframe, int64_t time_num, */ int oaknode_keyframe_set_time_undoable(OakNodeKeyframe *keyframe, int64_t time_num, int64_t time_den, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Read the keyframe's value mapped into `out`. Values without a @@ -132,7 +132,7 @@ int oaknode_keyframe_set_value(OakNodeKeyframe *keyframe, */ int oaknode_keyframe_set_value_undoable(OakNodeKeyframe *keyframe, const oaknode_value *v, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Read a string value. Two-stage getter. @@ -154,7 +154,7 @@ int oaknode_keyframe_set_value_string(OakNodeKeyframe *keyframe, */ int oaknode_keyframe_set_value_string_undoable(OakNodeKeyframe *keyframe, const char *value, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief The keyframe's interpolation type (oaknode_keyframe_type). @@ -171,7 +171,7 @@ int oaknode_keyframe_set_type(OakNodeKeyframe *keyframe, int type); * @brief Create a set-type command (same semantics as the live variant). */ int oaknode_keyframe_set_type_undoable(OakNodeKeyframe *keyframe, int type, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief A bezier control point (`handle` is an @@ -192,7 +192,7 @@ int oaknode_keyframe_set_bezier_control(OakNodeKeyframe *keyframe, int handle, */ int oaknode_keyframe_set_bezier_control_undoable(OakNodeKeyframe *keyframe, int handle, double x, double y, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief The keyframe's track index. diff --git a/include/node/node.h b/include/node/node.h index 7bb8397c1..017ed5fe7 100644 --- a/include/node/node.h +++ b/include/node/node.h @@ -171,7 +171,7 @@ int oaknode_node_set_label(OakNodeNode *node, const char *label); * @return OAKNODE_OK or a negative OAKNODE_E_* error code. */ int oaknode_node_set_label_undoable(OakNodeNode *node, const char *label, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief The node's override color index (Node::get_override_color(); @@ -196,7 +196,7 @@ int oaknode_node_set_override_color(OakNodeNode *node, int index); * @return OAKNODE_OK or a negative OAKNODE_E_* error code. */ int oaknode_node_set_override_color_undoable(OakNodeNode *node, int index, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief 1 if the node is enabled (the boolean "enabled_in" input's @@ -220,7 +220,7 @@ int oaknode_node_set_enabled(OakNodeNode *node, int enabled); * @return OAKNODE_OK or a negative OAKNODE_E_* error code. */ int oaknode_node_set_enabled_undoable(OakNodeNode *node, int enabled, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /* ---- Input introspection ------------------------------------------------ */ @@ -310,7 +310,7 @@ int oaknode_node_set_input(OakNodeNode *node, const char *input_id, */ int oaknode_node_set_input_undoable(OakNodeNode *node, const char *input_id, const oaknode_value *v, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Read a string-family input's standard value. Two-stage getter. @@ -331,7 +331,7 @@ int oaknode_node_set_input_string(OakNodeNode *node, const char *input_id, int oaknode_node_set_input_string_undoable(OakNodeNode *node, const char *input_id, const char *value, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /* ---- Graph editing -------------------------------------------------------- */ @@ -356,7 +356,7 @@ int oaknode_node_connect(OakNodeNode *output_node, OakNodeNode *input_node, int oaknode_node_connect_undoable(OakNodeNode *output_node, OakNodeNode *input_node, const char *input_id, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Remove the edge feeding `input_node`'s `input_id` directly @@ -371,7 +371,7 @@ int oaknode_node_disconnect(OakNodeNode *input_node, const char *input_id); */ int oaknode_node_disconnect_undoable(OakNodeNode *input_node, const char *input_id, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Number of outgoing edges (Node::output_connections()). @@ -422,7 +422,7 @@ int oaknode_node_unlink(OakNodeNode *a, OakNodeNode *b, int *out_unlinked); * `link` != 0 links, 0 unlinks). */ int oaknode_node_link_undoable(OakNodeNode *a, OakNodeNode *b, int link, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief 1 if the two nodes are linked (Node::are_linked()). @@ -477,7 +477,7 @@ int oaknode_node_set_context_position(OakNodeNode *node, OakNodeNode *context, int oaknode_node_set_context_position_undoable(OakNodeNode *node, OakNodeNode *context, double x, double y, int expanded, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Remove the node from `context` directly (live). @@ -504,7 +504,7 @@ OakNodeNode *oaknode_node_create_copy(const OakNodeNode *node); * until then. Returns NULL on failure. */ OakNodeNode *oaknode_node_copy_in_graph(OakNodeNode *node, - OakUndoCommand **out_command); + OakUndoCommand *out_command); /** * @brief Get the project this node belongs to (borrowed). *out may be @@ -537,7 +537,7 @@ int oaknode_node_disconnect_element(OakNodeNode *input_node, * @brief Create a command that adds a node to a project's graph * (olive::NodeAddCommand). Owned; free with oakundo_command_free(). */ -OakUndoCommand *oaknode_command_create_add_node(OakNodeProject *graph, +OakUndoCommand oaknode_command_create_add_node(OakNodeProject *graph, OakNodeNode *node); /** @@ -545,7 +545,7 @@ OakUndoCommand *oaknode_command_create_add_node(OakNodeProject *graph, * repositions its dependencies recursively * (olive::NodeSetPositionAndDependenciesRecursivelyCommand). Owned. */ -OakUndoCommand *oaknode_command_create_set_position_recursive( +OakUndoCommand oaknode_command_create_set_position_recursive( OakNodeNode *node, OakNodeNode *context, double x, double y); /** @@ -607,7 +607,7 @@ int oaknode_node_find_input_footage(const OakNodeNode *node, * Owned command handle; free with oakundo_command_free(). Returns NULL * on failure. */ -OakUndoCommand *oaknode_command_create_remove_node(OakNodeNode *node); +OakUndoCommand oaknode_command_create_remove_node(OakNodeNode *node); /** * @brief Destroy an OWNED node immediately (C++ delete). NULL is a no-op. diff --git a/include/task/project.h b/include/task/project.h index 6347f87ff..3be9c7675 100644 --- a/include/task/project.h +++ b/include/task/project.h @@ -57,7 +57,7 @@ OakTaskTask *oaktask_create_project_import(OakNodeFolder *folder, int url_count); /** @brief Take the import's undo command (ownership transfer). */ -OakUndoCommand *oaktask_import_take_command(OakTaskTask *t); +OakUndoCommand oaktask_import_take_command(OakTaskTask *t); int oaktask_import_footage_count(OakTaskTask *t); diff --git a/include/timeline/edit.h b/include/timeline/edit.h index fbe6ae0fe..aadd739b3 100644 --- a/include/timeline/edit.h +++ b/include/timeline/edit.h @@ -41,60 +41,60 @@ extern "C" { */ /** @brief olive::TimelineAddTrackCommand. */ -OakUndoCommand *oaktimeline_add_track_command(OakNodeTrackList *list); +OakUndoCommand oaktimeline_add_track_command(OakNodeTrackList *list); /** @brief olive::TimelineRemoveTrackCommand. */ -OakUndoCommand *oaktimeline_remove_track_command(OakNodeTrack *track); +OakUndoCommand oaktimeline_remove_track_command(OakNodeTrack *track); /** @brief olive::TrackPlaceBlockCommand. */ -OakUndoCommand *oaktimeline_place_block_command(OakNodeTrackList *list, +OakUndoCommand oaktimeline_place_block_command(OakNodeTrackList *list, int track_index, OakNodeBlock *block, int64_t in_num, int64_t in_den); /** @brief olive::TrackReplaceBlockWithGapCommand. */ -OakUndoCommand *oaktimeline_replace_block_with_gap_command( +OakUndoCommand oaktimeline_replace_block_with_gap_command( OakNodeTrack *track, OakNodeBlock *block); /** * @brief olive::BlockTrimCommand. `mode` is an OakTimelineMovementMode * value (k_trim_in / k_trim_out). */ -OakUndoCommand *oaktimeline_trim_command(OakNodeTrack *track, +OakUndoCommand oaktimeline_trim_command(OakNodeTrack *track, OakNodeBlock *block, int64_t new_length_num, int64_t new_length_den, int mode); /** @brief olive::BlockSplitCommand on a set of blocks at one point. */ -OakUndoCommand *oaktimeline_split_command(OakNodeBlock *const *blocks, +OakUndoCommand oaktimeline_split_command(OakNodeBlock *const *blocks, int count, int64_t point_num, int64_t point_den); /** @brief olive::BlockSplitPreservingLinksCommand. */ -OakUndoCommand *oaktimeline_split_preserving_links_command( +OakUndoCommand oaktimeline_split_preserving_links_command( OakNodeBlock *const *blocks, int count, const int64_t *point_nums, const int64_t *point_dens, int time_count); /** @brief olive::TimelineRippleDeleteGapsAtRegionsCommand. */ -OakUndoCommand *oaktimeline_ripple_delete_gaps_command( +OakUndoCommand oaktimeline_ripple_delete_gaps_command( OakNodeSequence *sequence, const int64_t *in_nums, const int64_t *in_dens, const int64_t *out_nums, const int64_t *out_dens, OakNodeTrack *const *tracks, int range_count); /** @brief olive::TrackSlideCommand. */ -OakUndoCommand *oaktimeline_slide_command( +OakUndoCommand oaktimeline_slide_command( OakNodeTrack *track, OakNodeBlock *const *blocks, int block_count, OakNodeBlock *in_adjacent, OakNodeBlock *out_adjacent, int64_t movement_num, int64_t movement_den); /** @brief olive::TrackRippleRemoveAreaCommand. */ -OakUndoCommand *oaktimeline_ripple_remove_area_command( +OakUndoCommand oaktimeline_ripple_remove_area_command( OakNodeTrack *track, int64_t in_num, int64_t in_den, int64_t out_num, int64_t out_den); /** @brief olive::TrackListInsertGaps. */ -OakUndoCommand *oaktimeline_insert_gaps_command(OakNodeTrackList *list, +OakUndoCommand oaktimeline_insert_gaps_command(OakNodeTrackList *list, int64_t point_num, int64_t point_den, int64_t length_num, diff --git a/include/timeline/marker.h b/include/timeline/marker.h index 81d35a73c..ce914214a 100644 --- a/include/timeline/marker.h +++ b/include/timeline/marker.h @@ -65,7 +65,7 @@ int oaktimeline_marker_at(const OakTimelineMarkerList *list, int index, * Owned command; free with oakundo_command_free(). Redo it directly or * push it on an undo stack. Returns NULL on failure. */ -OakUndoCommand *oaktimeline_marker_add_command( +OakUndoCommand oaktimeline_marker_add_command( OakTimelineMarkerList *list, int in_num, int in_den, int out_num, int out_den, const char *name, int color); @@ -74,13 +74,13 @@ OakUndoCommand *oaktimeline_marker_add_command( * OAKTIMELINE_E_NOT_FOUND (as NULL result documented by error) is * reported by returning NULL. */ -OakUndoCommand *oaktimeline_marker_remove_at_command( +OakUndoCommand oaktimeline_marker_remove_at_command( OakTimelineMarkerList *list, int index); /** * @brief Create a command that sets a marker's time range. */ -OakUndoCommand *oaktimeline_marker_set_time_command( +OakUndoCommand oaktimeline_marker_set_time_command( OakTimelineMarkerList *list, int index, int in_num, int in_den, int out_num, int out_den); @@ -89,7 +89,7 @@ OakUndoCommand *oaktimeline_marker_set_time_command( * `name` may be NULL to leave the name unchanged (color still applies * when >= 0; both NULL-name and color < 0 is a no-op error). */ -OakUndoCommand *oaktimeline_marker_set_props_command( +OakUndoCommand oaktimeline_marker_set_props_command( OakTimelineMarkerList *list, int index, int color, const char *name); /** diff --git a/include/timeline/workarea.h b/include/timeline/workarea.h index 9e5598f09..5bb90028a 100644 --- a/include/timeline/workarea.h +++ b/include/timeline/workarea.h @@ -60,7 +60,7 @@ int oaktimeline_workarea_set_range(OakTimelineWorkArea *w, int in_num, * The old range must be supplied by the caller (facade knows what it * changed from). Owned; free with oakundo_command_free(). */ -OakUndoCommand *oaktimeline_workarea_set_range_command( +OakUndoCommand oaktimeline_workarea_set_range_command( OakTimelineWorkArea *w, int in_num, int in_den, int out_num, int out_den, int old_in_num, int old_in_den, int old_out_num, int old_out_den); @@ -68,7 +68,7 @@ OakUndoCommand *oaktimeline_workarea_set_range_command( /** * @brief Create a set-enabled command (olive::WorkareaSetEnabledCommand). */ -OakUndoCommand *oaktimeline_workarea_set_enabled_command( +OakUndoCommand oaktimeline_workarea_set_enabled_command( OakTimelineWorkArea *w, int enabled); /** diff --git a/include/undo/undocommand.h b/include/undo/undocommand.h index 95c0ae456..902c219d5 100644 --- a/include/undo/undocommand.h +++ b/include/undo/undocommand.h @@ -21,31 +21,43 @@ #ifndef OAK_EDITOR_UNDO_UNDOCOMMAND_H #define OAK_EDITOR_UNDO_UNDOCOMMAND_H +#include + #include "undo/error.h" #ifdef __cplusplus extern "C" { #endif +#define OAKUNDO_ABI_VERSION 1 + /** - * @brief Opaque handle to an undo command (olive::UndoCommand). + * @brief Reference-counted handle to an undo command + * (olive::UndoCommand). * - * Handles created by oakundo_command_init() / - * oakundo_command_init_multi() own the underlying command and must be - * released with oakundo_command_free() UNLESS they are pushed onto an - * OakUndoStack, which takes ownership. Handles returned by - * oakundo_command_multi_child() are borrowed wrappers: free the wrapper - * with oakundo_command_free(), the underlying command stays owned by the - * multi command. + * The object never leaves the library that created it; every external + * reference is one of these handles. Semantics are shared_ptr-like: + * init/factory functions return a handle with count 1, addref(ctx) + * takes another reference, release(ctx) drops one and the library + * destroys the object when the count reaches zero. + * + * Pushing a command onto an OakUndoStack transfers one reference to the + * stack (the stack releases it when the command is discarded); callers + * may keep their own reference or release it right after the push. */ -typedef struct OakUndoCommand OakUndoCommand; +typedef struct OakUndoCommand { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKUNDO_ABI_VERSION. */ +} OakUndoCommand; /** * @brief Callback table backing a caller-defined undo command. * * Any callback may be NULL; a NULL redo/undo makes that direction a - * no-op. free_fn is invoked when the command is destroyed (whether pushed - * onto a stack or freed directly) and releases userdata. + * no-op. free_fn is invoked when the command is destroyed (whether held + * by a stack or released directly) and releases userdata. */ typedef struct OakUndoCommandVtable { void (*redo)(void *userdata); @@ -58,30 +70,30 @@ typedef struct OakUndoCommandVtable { * * The command takes ownership of `userdata`; `vtable` is copied. * - * @return Command handle, or NULL on invalid argument or allocation - * failure. + * @return Command handle with count 1; ctx is NULL on invalid argument + * or allocation failure. */ -OakUndoCommand *oakundo_command_init(const OakUndoCommandVtable *vtable, - void *userdata); +OakUndoCommand oakundo_command_init(const OakUndoCommandVtable *vtable, + void *userdata); /** * @brief Create an empty multi command (olive::MultiUndoCommand). * - * @return Command handle, or NULL on allocation failure. + * @return Command handle with count 1; ctx is NULL on allocation + * failure. */ -OakUndoCommand *oakundo_command_init_multi(void); +OakUndoCommand oakundo_command_init_multi(void); /** * @brief Add `child` to the multi command `multi`. * - * On success the multi command takes ownership of the underlying child - * command; the child handle wrapper is consumed and must not be used or - * freed afterwards. + * The multi command takes one reference to the child; the caller keeps + * its own reference and may release it after the call. * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_command_multi_add_child(OakUndoCommand *multi, - OakUndoCommand *child); +int oakundo_command_multi_add_child(OakUndoCommand multi, + OakUndoCommand child); /** * @brief Query the number of children in a multi command. @@ -90,19 +102,20 @@ int oakundo_command_multi_add_child(OakUndoCommand *multi, * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_command_multi_child_count(OakUndoCommand *multi, int *out_count); +int oakundo_command_multi_child_count(OakUndoCommand multi, + int *out_count); /** - * @brief Borrow a handle to the child at `index` of a multi command. + * @brief Reference to the child at `index` of a multi command. * - * The returned handle is a wrapper owned by the caller (free with - * oakundo_command_free()); the underlying command is owned by `multi`. + * The returned handle carries its own reference; release it with + * oakundo_command_free(). * * @return OAKUNDO_OK, OAKUNDO_E_NOT_FOUND for an out-of-range index, or * another negative OAKUNDO_E_* error code. */ -int oakundo_command_multi_child(OakUndoCommand *multi, int index, - OakUndoCommand **out_child); +int oakundo_command_multi_child(OakUndoCommand multi, int index, + OakUndoCommand *out_child); /** * @brief Execute the command's redo without a stack @@ -110,7 +123,7 @@ int oakundo_command_multi_child(OakUndoCommand *multi, int index, * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_command_redo_now(OakUndoCommand *command); +int oakundo_command_redo_now(OakUndoCommand command); /** * @brief Execute the command's undo without a stack @@ -118,15 +131,14 @@ int oakundo_command_redo_now(OakUndoCommand *command); * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_command_undo_now(OakUndoCommand *command); +int oakundo_command_undo_now(OakUndoCommand command); /** - * @brief Destroy a command handle. + * @brief Release one reference to a command handle. * - * Owned handles destroy the underlying command; borrowed handles (from - * oakundo_command_multi_child()) only destroy the wrapper. Commands - * pushed onto an OakUndoStack are owned by the stack and must not be - * freed by the caller. NULL is a no-op. + * Convenience wrapper around handle.release(handle.ctx): destroys the + * command when the count reaches zero. NULL handle or NULL ctx is a + * no-op; clears `command->ctx` after releasing. */ void oakundo_command_free(OakUndoCommand *command); diff --git a/include/undo/undostack.h b/include/undo/undostack.h index 31312cab1..494297fae 100644 --- a/include/undo/undostack.h +++ b/include/undo/undostack.h @@ -31,50 +31,59 @@ extern "C" { #endif /** - * @brief Opaque handle to an undo stack (olive::UndoStack). + * @brief Reference-counted handle to an undo stack (olive::UndoStack). + * + * Same ownership/count semantics as OakUndoCommand (see + * undo/undocommand.h). */ -typedef struct OakUndoStack OakUndoStack; +typedef struct OakUndoStack { + void *ctx; /**< Opaque pointer to the reference-counted object. */ + void (*addref)(void *ctx); /**< Atomically increments the count. */ + void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */ + uint32_t abi_version; /**< OAKUNDO_ABI_VERSION. */ +} OakUndoStack; /** - * @brief Create an undo stack. + * @brief Create an undo stack (count 1). * * A fresh stack contains a single "New/Open Project" empty command, * matching olive::UndoStack::clear(). * - * @return Stack handle, or NULL on allocation failure. + * @return Stack handle; ctx is NULL on allocation failure. */ -OakUndoStack *oakundo_undostack_init(void); +OakUndoStack oakundo_undostack_init(void); /** - * @brief Destroy an undo stack and all commands it owns. + * @brief Release one reference to an undo stack. * - * NULL is a no-op. + * NULL handle or NULL ctx is a no-op; clears `stack->ctx` after + * releasing. */ void oakundo_undostack_free(OakUndoStack *stack); /** * @brief Push `command` onto the stack and execute its redo. * - * On success the stack takes ownership of the underlying command; the - * handle wrapper is consumed and must not be used or freed afterwards. - * An empty multi command is deleted immediately (not pushed), matching - * olive::UndoStack::push. `name` is the user-visible label (NULL behaves - * like an empty label). + * The stack takes one reference to the command; the caller keeps its + * own reference and may release it after the call. An empty multi + * command is deleted immediately (not pushed), matching + * olive::UndoStack::push. `name` is the user-visible label (NULL + * behaves like an empty label). * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_push(OakUndoStack *stack, OakUndoCommand *command, +int oakundo_undostack_push(OakUndoStack stack, OakUndoCommand command, const char *name); /** * @brief Push a command that has already been executed (redo skipped). * - * Ownership rules match oakundo_undostack_push(). + * Reference rules match oakundo_undostack_push(). * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_push_pre_executed(OakUndoStack *stack, - OakUndoCommand *command, +int oakundo_undostack_push_pre_executed(OakUndoStack stack, + OakUndoCommand command, const char *name); /** @@ -82,14 +91,14 @@ int oakundo_undostack_push_pre_executed(OakUndoStack *stack, * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_undo(OakUndoStack *stack); +int oakundo_undostack_undo(OakUndoStack stack); /** * @brief Redo the most recently undone command, if any. * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_redo(OakUndoStack *stack); +int oakundo_undostack_redo(OakUndoStack stack); /** * @brief Undo/redo until the done-command count equals `index` @@ -97,7 +106,7 @@ int oakundo_undostack_redo(OakUndoStack *stack); * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_jump(OakUndoStack *stack, int64_t index); +int oakundo_undostack_jump(OakUndoStack stack, int64_t index); /** * @brief Delete all commands and push the fresh "New/Open Project" empty @@ -105,7 +114,7 @@ int oakundo_undostack_jump(OakUndoStack *stack, int64_t index); * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_clear(OakUndoStack *stack); +int oakundo_undostack_clear(OakUndoStack stack); /** * @brief Query whether undo (redo) is currently possible. @@ -114,8 +123,8 @@ int oakundo_undostack_clear(OakUndoStack *stack); * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_can_undo(OakUndoStack *stack, int *out_value); -int oakundo_undostack_can_redo(OakUndoStack *stack, int *out_value); +int oakundo_undostack_can_undo(OakUndoStack stack, int *out_value); +int oakundo_undostack_can_redo(OakUndoStack stack, int *out_value); /** * @brief Total number of history rows (done + undone commands). @@ -124,7 +133,7 @@ int oakundo_undostack_can_redo(OakUndoStack *stack, int *out_value); * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_count(OakUndoStack *stack, int64_t *out_count); +int oakundo_undostack_count(OakUndoStack stack, int64_t *out_count); /** * @brief Current position in the history: the number of done commands @@ -134,7 +143,7 @@ int oakundo_undostack_count(OakUndoStack *stack, int64_t *out_count); * * @return OAKUNDO_OK or a negative OAKUNDO_E_* error code. */ -int oakundo_undostack_index(OakUndoStack *stack, int64_t *out_index); +int oakundo_undostack_index(OakUndoStack stack, int64_t *out_index); /** * @brief Label of the history row at `row` (0-based, two-stage getter). @@ -143,7 +152,7 @@ int oakundo_undostack_index(OakUndoStack *stack, int64_t *out_index); * (non-negative), OAKUNDO_E_NOT_FOUND for an invalid row, or * another negative OAKUNDO_E_* error code. */ -int oakundo_undostack_command_text(OakUndoStack *stack, int64_t row, +int oakundo_undostack_command_text(OakUndoStack stack, int64_t row, char *buf, int buf_size); /** @@ -154,7 +163,7 @@ int oakundo_undostack_command_text(OakUndoStack *stack, int64_t row, * @return OAKUNDO_OK, OAKUNDO_E_NOT_FOUND for an invalid row, or another * negative OAKUNDO_E_* error code. */ -int oakundo_undostack_command_is_done(OakUndoStack *stack, int64_t row, +int oakundo_undostack_command_is_done(OakUndoStack stack, int64_t row, int *out_value); #ifdef __cplusplus diff --git a/src/node/c_api/folder.cpp b/src/node/c_api/folder.cpp index 07c8d4ef6..14213c029 100644 --- a/src/node/c_api/folder.cpp +++ b/src/node/c_api/folder.cpp @@ -238,24 +238,20 @@ OakNodeNode *oaknode_folder_as_node(OakNodeFolder *folder) return reinterpret_cast(folder); } -OakUndoCommand *oaknode_command_create_folder_add_child( +OakUndoCommand oaknode_command_create_folder_add_child( OakNodeFolder *folder, OakNodeNode *child) { if (!folder || !child) { - return NULL; + return OakUndoCommand{}; } try { - OakUndoCommand *handle = - new (std::nothrow) OakUndoCommand{ nullptr, true }; - if (!handle) { - return NULL; - } - handle->command = new olive::FolderAddChild( - reinterpret_cast(folder), - reinterpret_cast(child)); - return handle; + return oakundo_capi::make_command_handle( + new olive::FolderAddChild( + reinterpret_cast(folder), + reinterpret_cast(child)), + true); } catch (...) { - return NULL; + return OakUndoCommand{}; } } diff --git a/src/node/c_api/group.cpp b/src/node/c_api/group.cpp index d49d6b34c..ab7b49475 100644 --- a/src/node/c_api/group.cpp +++ b/src/node/c_api/group.cpp @@ -111,18 +111,18 @@ int oaknode_group_add_input_passthrough_undoable(OakNodeGroup *group, OakNodeNode *node, const char *input_id, int element, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!group || !node || !input_id || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeGroupAddInputPassthrough( to_group(group), olive::NodeInput(to_node(node), input_id, element))); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -246,17 +246,17 @@ int oaknode_group_set_output_passthrough(OakNodeGroup *group, } int oaknode_group_set_output_passthrough_undoable( - OakNodeGroup *group, OakNodeNode *node, OakUndoCommand **out_command) + OakNodeGroup *group, OakNodeNode *node, OakUndoCommand *out_command) { if (!group || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeGroupSetOutputPassthrough(to_group(group), to_node(node))); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; diff --git a/src/node/c_api/keyframe.cpp b/src/node/c_api/keyframe.cpp index 50bed3625..36968c813 100644 --- a/src/node/c_api/keyframe.cpp +++ b/src/node/c_api/keyframe.cpp @@ -213,19 +213,19 @@ int oaknode_keyframe_set_time(OakNodeKeyframe *keyframe, int64_t time_num, int oaknode_keyframe_set_time_undoable(OakNodeKeyframe *keyframe, int64_t time_num, int64_t time_den, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!keyframe || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeParamSetKeyframeTimeCommand( to_key(keyframe), olive::core::Rational(static_cast(time_num), static_cast(time_den)))); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -316,7 +316,7 @@ int oaknode_keyframe_set_value(OakNodeKeyframe *keyframe, int oaknode_keyframe_set_value_undoable(OakNodeKeyframe *keyframe, const oaknode_value *v, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!keyframe || !v || !out_command) { return OAKNODE_E_INVALID; @@ -328,10 +328,10 @@ int oaknode_keyframe_set_value_undoable(OakNodeKeyframe *keyframe, return OAKNODE_E_INVALID; } - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeParamSetKeyframeValueCommand(to_key(keyframe), variant)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -373,17 +373,17 @@ int oaknode_keyframe_set_value_string(OakNodeKeyframe *keyframe, int oaknode_keyframe_set_value_string_undoable(OakNodeKeyframe *keyframe, const char *value, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!keyframe || !value || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeParamSetKeyframeValueCommand( to_key(keyframe), olive::Variant(value))); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -426,7 +426,7 @@ int oaknode_keyframe_set_type(OakNodeKeyframe *keyframe, int type) } int oaknode_keyframe_set_type_undoable(OakNodeKeyframe *keyframe, int type, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!keyframe || !out_command) { return OAKNODE_E_INVALID; @@ -438,9 +438,9 @@ int oaknode_keyframe_set_type_undoable(OakNodeKeyframe *keyframe, int type, return OAKNODE_E_INVALID; } - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new KeyframeSetTypeCommand(to_key(keyframe), keyframe_type)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -498,7 +498,7 @@ int oaknode_keyframe_set_bezier_control(OakNodeKeyframe *keyframe, int handle, int oaknode_keyframe_set_bezier_control_undoable(OakNodeKeyframe *keyframe, int handle, double x, double y, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!keyframe || !out_command) { return OAKNODE_E_INVALID; @@ -514,10 +514,10 @@ int oaknode_keyframe_set_bezier_control_undoable(OakNodeKeyframe *keyframe, return OAKNODE_E_INVALID; } - OakUndoCommand *handle_ptr = oaknode_c_api::wrap_command( + OakUndoCommand handle_ptr = oaknode_c_api::wrap_command( new KeyframeSetBezierControlCommand(to_key(keyframe), bezier_handle, olive::PointF(x, y))); - if (!handle_ptr) { + if (!handle_ptr.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle_ptr; diff --git a/src/node/c_api/node.cpp b/src/node/c_api/node.cpp index 7d17ed4ca..b5545d351 100644 --- a/src/node/c_api/node.cpp +++ b/src/node/c_api/node.cpp @@ -167,16 +167,16 @@ int oaknode_node_set_label(OakNodeNode *node, const char *label) } int oaknode_node_set_label_undoable(OakNodeNode *node, const char *label, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !label || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeRenameCommand(to_node(node), label)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -215,16 +215,16 @@ int oaknode_node_set_override_color(OakNodeNode *node, int index) } int oaknode_node_set_override_color_undoable(OakNodeNode *node, int index, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeOverrideColorCommand(to_node(node), index)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -268,7 +268,7 @@ int oaknode_node_set_enabled(OakNodeNode *node, int enabled) } int oaknode_node_set_enabled_undoable(OakNodeNode *node, int enabled, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !out_command) { return OAKNODE_E_INVALID; @@ -279,9 +279,9 @@ int oaknode_node_set_enabled_undoable(OakNodeNode *node, int enabled, olive::SplitValue split = olive::NodeValue::split_normal_value_into_track_values( olive::NodeValue::k_boolean, olive::Variant(enabled != 0)); - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeParamSetSplitStandardValueCommand(ref, split)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -464,7 +464,7 @@ int oaknode_node_set_input(OakNodeNode *node, const char *input_id, int oaknode_node_set_input_undoable(OakNodeNode *node, const char *input_id, const oaknode_value *v, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !out_command) { return OAKNODE_E_INVALID; @@ -485,9 +485,9 @@ int oaknode_node_set_input_undoable(OakNodeNode *node, const char *input_id, olive::SplitValue split = olive::NodeValue::split_normal_value_into_track_values(type, variant); - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeParamSetSplitStandardValueCommand(ref, split)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -545,7 +545,7 @@ int oaknode_node_set_input_string(OakNodeNode *node, const char *input_id, int oaknode_node_set_input_string_undoable(OakNodeNode *node, const char *input_id, const char *value, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !value || !out_command) { return OAKNODE_E_INVALID; @@ -566,9 +566,9 @@ int oaknode_node_set_input_string_undoable(OakNodeNode *node, to_node(node)->get_input_data_type(input_id), olive::Variant(value)); - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeParamSetSplitStandardValueCommand(ref, split)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -610,7 +610,7 @@ int oaknode_node_connect(OakNodeNode *output_node, OakNodeNode *input_node, int oaknode_node_connect_undoable(OakNodeNode *output_node, OakNodeNode *input_node, const char *input_id, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!output_node || !input_node || !out_command) { return OAKNODE_E_INVALID; @@ -624,11 +624,11 @@ int oaknode_node_connect_undoable(OakNodeNode *output_node, return OAKNODE_E_INVALID; } - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeEdgeAddCommand( to_node(output_node), olive::NodeInput(to_node(input_node), input_id))); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -663,7 +663,7 @@ int oaknode_node_disconnect(OakNodeNode *input_node, const char *input_id) int oaknode_node_disconnect_undoable(OakNodeNode *input_node, const char *input_id, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!input_node || !out_command) { return OAKNODE_E_INVALID; @@ -678,10 +678,10 @@ int oaknode_node_disconnect_undoable(OakNodeNode *input_node, return OAKNODE_E_NOT_FOUND; } - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeEdgeRemoveCommand( output, olive::NodeInput(to_node(input_node), input_id))); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -804,16 +804,16 @@ int oaknode_node_unlink(OakNodeNode *a, OakNodeNode *b, int *out_unlinked) } int oaknode_node_link_undoable(OakNodeNode *a, OakNodeNode *b, int link, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!a || !b || !out_command) { return OAKNODE_E_INVALID; } try { - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeLinkCommand(to_node(a), to_node(b), link != 0)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -964,7 +964,7 @@ int oaknode_node_set_context_position(OakNodeNode *node, OakNodeNode *context, int oaknode_node_set_context_position_undoable(OakNodeNode *node, OakNodeNode *context, double x, double y, int expanded, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !context || !out_command) { return OAKNODE_E_INVALID; @@ -977,10 +977,10 @@ int oaknode_node_set_context_position_undoable(OakNodeNode *node, // position map lives on the positioned node keyed by the context, // so the command's (node, context) arguments are swapped relative // to this function's signature. - OakUndoCommand *handle = oaknode_c_api::wrap_command( + OakUndoCommand handle = oaknode_c_api::wrap_command( new olive::NodeSetPositionCommand(to_node(context), to_node(node), position)); - if (!handle) { + if (!handle.ctx) { return OAKNODE_E_NOMEM; } *out_command = handle; @@ -1039,7 +1039,7 @@ void oaknode_node_free(OakNodeNode *node) } OakNodeNode *oaknode_node_copy_in_graph(OakNodeNode *node, - OakUndoCommand **out_command) + OakUndoCommand *out_command) { if (!node || !out_command) { return NULL; @@ -1054,7 +1054,7 @@ OakNodeNode *oaknode_node_copy_in_graph(OakNodeNode *node, return NULL; } *out_command = oaknode_c_api::wrap_command(command); - if (!*out_command) { + if (!out_command->ctx) { delete command; return NULL; } @@ -1065,10 +1065,10 @@ OakNodeNode *oaknode_node_copy_in_graph(OakNodeNode *node, } } -OakUndoCommand *oaknode_command_create_remove_node(OakNodeNode *node) +OakUndoCommand oaknode_command_create_remove_node(OakNodeNode *node) { if (!node) { - return NULL; + return OakUndoCommand{}; } try { @@ -1076,7 +1076,7 @@ OakUndoCommand *oaknode_command_create_remove_node(OakNodeNode *node) new olive::NodeRemoveWithExclusiveDependenciesAndDisconnect( to_node(node))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } @@ -1171,11 +1171,11 @@ int oaknode_node_disconnect_element(OakNodeNode *input_node, } } -OakUndoCommand *oaknode_command_create_add_node(OakNodeProject *graph, +OakUndoCommand oaknode_command_create_add_node(OakNodeProject *graph, OakNodeNode *node) { if (!graph || !node) { - return NULL; + return OakUndoCommand{}; } try { @@ -1183,15 +1183,15 @@ OakUndoCommand *oaknode_command_create_add_node(OakNodeProject *graph, new olive::NodeAddCommand( reinterpret_cast(graph), to_node(node))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaknode_command_create_set_position_recursive( +OakUndoCommand oaknode_command_create_set_position_recursive( OakNodeNode *node, OakNodeNode *context, double x, double y) { if (!node || !context) { - return NULL; + return OakUndoCommand{}; } try { @@ -1200,7 +1200,7 @@ OakUndoCommand *oaknode_command_create_set_position_recursive( to_node(node), to_node(context), olive::Node::Position(olive::PointF(x, y)))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } diff --git a/src/node/c_api/valueconvert.h b/src/node/c_api/valueconvert.h index 395997ad2..cc886a005 100644 --- a/src/node/c_api/valueconvert.h +++ b/src/node/c_api/valueconvert.h @@ -250,22 +250,15 @@ inline int value_from_variant(olive::NodeValue::Type type, const olive::Variant /** * @brief Wrap a freshly created olive::UndoCommand in an owned - * OakUndoCommand handle. Returns NULL on allocation failure. + * OakUndoCommand handle (reference count 1). Returns an empty handle + * (ctx == NULL) on allocation failure. */ -inline OakUndoCommand *wrap_command(olive::UndoCommand *command) +inline OakUndoCommand wrap_command(olive::UndoCommand *command) { if (!command) { - return NULL; + return OakUndoCommand{}; } - - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand(); - if (!handle) { - delete command; - return NULL; - } - handle->command = command; - handle->owned = true; - return handle; + return oakundo_capi::make_command_handle(command, true); } } diff --git a/src/node/tests/group_test.cpp b/src/node/tests/group_test.cpp index 58ec111f5..ca0f522d1 100644 --- a/src/node/tests/group_test.cpp +++ b/src/node/tests/group_test.cpp @@ -136,11 +136,11 @@ TEST(NodeGroupTest, PassthroughAddUndoable) OakNodeGroup *group = make_group_with_inner(as_handle(&inner)); ASSERT_NE(group, nullptr); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_group_add_input_passthrough_undoable( group, as_handle(&inner), "float_in", -1, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); int count = 0; EXPECT_EQ(oaknode_group_passthrough_count(group, &count), OAKNODE_OK); @@ -154,7 +154,7 @@ TEST(NodeGroupTest, PassthroughAddUndoable) EXPECT_EQ(oaknode_group_passthrough_count(group, &count), OAKNODE_OK); EXPECT_EQ(count, 0); - oakundo_command_free(command); + oakundo_command_free(&command); oaknode_group_free(group); } @@ -174,18 +174,18 @@ TEST(NodeGroupTest, OutputPassthroughLiveAndUndoable) EXPECT_EQ(oaknode_group_get_output_passthrough(group, &out), OAKNODE_OK); EXPECT_EQ(out, inner_handle); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_group_set_output_passthrough_undoable(group, nullptr, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_group_get_output_passthrough(group, &out), OAKNODE_OK); EXPECT_EQ(out, nullptr); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_group_get_output_passthrough(group, &out), OAKNODE_OK); EXPECT_EQ(out, inner_handle); - oakundo_command_free(command); + oakundo_command_free(&command); EXPECT_EQ(oaknode_group_set_output_passthrough(nullptr, inner_handle), OAKNODE_E_INVALID); diff --git a/src/node/tests/keyframe_test.cpp b/src/node/tests/keyframe_test.cpp index 0d4e77506..1fa5d84e9 100644 --- a/src/node/tests/keyframe_test.cpp +++ b/src/node/tests/keyframe_test.cpp @@ -124,10 +124,10 @@ TEST(NodeKeyframeTest, TimeLiveAndUndoable) EXPECT_EQ(num, 1); EXPECT_EQ(den, 2); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_keyframe_set_time_undoable(key, 5, 1, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oaknode_keyframe_get_time(key, &num, &den), OAKNODE_OK); EXPECT_EQ(num, 1); // not yet executed @@ -138,7 +138,7 @@ TEST(NodeKeyframeTest, TimeLiveAndUndoable) EXPECT_EQ(oaknode_keyframe_get_time(key, &num, &den), OAKNODE_OK); EXPECT_EQ(num, 1); - oakundo_command_free(command); + oakundo_command_free(&command); oaknode_keyframe_free(key); EXPECT_EQ(oaknode_keyframe_set_time(nullptr, 0, 1), OAKNODE_E_INVALID); @@ -163,17 +163,17 @@ TEST(NodeKeyframeTest, ValueLiveAndUndoable) EXPECT_DOUBLE_EQ(out.f[0], 3.25); in = make_float(7.0); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_keyframe_set_value_undoable(key, &in, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_value(key, &out), OAKNODE_OK); EXPECT_DOUBLE_EQ(out.f[0], 7.0); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_value(key, &out), OAKNODE_OK); EXPECT_DOUBLE_EQ(out.f[0], 3.25); - oakundo_command_free(command); + oakundo_command_free(&command); // STRING rejected by the POD setter. in.type = OAKNODE_VALUE_STRING; @@ -195,18 +195,18 @@ TEST(NodeKeyframeTest, StringValueLiveAndUndoable) EXPECT_EQ(oaknode_keyframe_get_value_string(key, buf, sizeof(buf)), 6); EXPECT_STREQ(buf, "hello"); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_keyframe_set_value_string_undoable(key, "world", &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_value_string(key, buf, sizeof(buf)), 6); EXPECT_STREQ(buf, "world"); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_value_string(key, buf, sizeof(buf)), 6); EXPECT_STREQ(buf, "hello"); - oakundo_command_free(command); + oakundo_command_free(&command); EXPECT_EQ(oaknode_keyframe_set_value_string(key, nullptr), OAKNODE_E_INVALID); @@ -229,18 +229,18 @@ TEST(NodeKeyframeTest, TypeLiveAndUndoable) EXPECT_EQ(oaknode_keyframe_get_type(key, &type), OAKNODE_OK); EXPECT_EQ(type, OAKNODE_KEYFRAME_HOLD); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_keyframe_set_type_undoable(key, OAKNODE_KEYFRAME_BEZIER, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_type(key, &type), OAKNODE_OK); EXPECT_EQ(type, OAKNODE_KEYFRAME_BEZIER); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_type(key, &type), OAKNODE_OK); EXPECT_EQ(type, OAKNODE_KEYFRAME_HOLD); - oakundo_command_free(command); + oakundo_command_free(&command); EXPECT_EQ(oaknode_keyframe_set_type(key, 99), OAKNODE_E_INVALID); EXPECT_EQ(oaknode_keyframe_set_type(nullptr, OAKNODE_KEYFRAME_HOLD), @@ -266,11 +266,11 @@ TEST(NodeKeyframeTest, BezierControlLiveAndUndoable) EXPECT_DOUBLE_EQ(x, -1.0); EXPECT_DOUBLE_EQ(y, 0.5); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_keyframe_set_bezier_control_undoable( key, OAKNODE_KEYFRAME_OUT_HANDLE, 2.0, -0.5, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_keyframe_get_bezier_control( key, OAKNODE_KEYFRAME_OUT_HANDLE, &x, &y), @@ -283,7 +283,7 @@ TEST(NodeKeyframeTest, BezierControlLiveAndUndoable) OAKNODE_OK); EXPECT_DOUBLE_EQ(x, 0.0); EXPECT_DOUBLE_EQ(y, 0.0); - oakundo_command_free(command); + oakundo_command_free(&command); EXPECT_EQ(oaknode_keyframe_set_bezier_control(key, 99, 0.0, 0.0), OAKNODE_E_INVALID); diff --git a/src/node/tests/node_test.cpp b/src/node/tests/node_test.cpp index e43cdba78..da00824c1 100644 --- a/src/node/tests/node_test.cpp +++ b/src/node/tests/node_test.cpp @@ -117,10 +117,10 @@ TEST(NodeMetadataTest, LabelUndoableSymmetry) EXPECT_EQ(oaknode_node_set_label(handle, "before"), OAKNODE_OK); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_set_label_undoable(handle, "after", &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(get_string(oaknode_node_get_label, handle), "before"); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); @@ -128,7 +128,7 @@ TEST(NodeMetadataTest, LabelUndoableSymmetry) EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(get_string(oaknode_node_get_label, handle), "before"); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(NodeMetadataTest, OverrideColorLiveAndUndoable) @@ -144,17 +144,17 @@ TEST(NodeMetadataTest, OverrideColorLiveAndUndoable) EXPECT_EQ(oaknode_node_get_override_color(handle, &color), OAKNODE_OK); EXPECT_EQ(color, 3); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_set_override_color_undoable(handle, 7, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_get_override_color(handle, &color), OAKNODE_OK); EXPECT_EQ(color, 7); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_get_override_color(handle, &color), OAKNODE_OK); EXPECT_EQ(color, 3); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(NodeMetadataTest, EnabledLiveAndUndoable) @@ -170,17 +170,17 @@ TEST(NodeMetadataTest, EnabledLiveAndUndoable) EXPECT_EQ(oaknode_node_is_enabled(handle, &enabled), OAKNODE_OK); EXPECT_EQ(enabled, 0); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_set_enabled_undoable(handle, 1, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_is_enabled(handle, &enabled), OAKNODE_OK); EXPECT_EQ(enabled, 1); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_is_enabled(handle, &enabled), OAKNODE_OK); EXPECT_EQ(enabled, 0); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(NodeInputTest, EnumerateInputs) @@ -302,10 +302,10 @@ TEST(NodeInputTest, ValueUndoableSymmetry) OakNodeNode *handle = as_handle(&node); oaknode_value in = make_float(9.0); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_set_input_undoable(handle, "float_in", &in, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); oaknode_value out; EXPECT_EQ(oaknode_node_get_input(handle, "float_in", &out), OAKNODE_OK); @@ -317,7 +317,7 @@ TEST(NodeInputTest, ValueUndoableSymmetry) EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_get_input(handle, "float_in", &out), OAKNODE_OK); EXPECT_DOUBLE_EQ(out.f[0], 0.0); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(NodeInputTest, StringValueLiveAndUndoable) @@ -343,11 +343,11 @@ TEST(NodeInputTest, StringValueLiveAndUndoable) EXPECT_EQ(oaknode_node_set_input_string(handle, "unknown_in", "x"), OAKNODE_E_NOT_FOUND); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_set_input_string_undoable(handle, "text_in", "world", &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_get_input_string(handle, "text_in", buf, sizeof(buf)), @@ -358,7 +358,7 @@ TEST(NodeInputTest, StringValueLiveAndUndoable) sizeof(buf)), 6); EXPECT_STREQ(buf, "hello"); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(NodeGraphTest, ConnectDisconnectLive) @@ -424,10 +424,10 @@ TEST(NodeGraphTest, ConnectDisconnectUndoable) OakNodeNode *src = as_handle(&source); OakNodeNode *dst = as_handle(&dest); - OakUndoCommand *add = nullptr; + OakUndoCommand add = {}; EXPECT_EQ(oaknode_node_connect_undoable(src, dst, "float_in", &add), OAKNODE_OK); - ASSERT_NE(add, nullptr); + ASSERT_NE(add.ctx, nullptr); int connected = 0; EXPECT_EQ(oakundo_command_redo_now(add), OAKUNDO_OK); @@ -442,10 +442,10 @@ TEST(NodeGraphTest, ConnectDisconnectUndoable) // Redo again so the remove command has an edge to work on. EXPECT_EQ(oakundo_command_redo_now(add), OAKUNDO_OK); - OakUndoCommand *remove = nullptr; + OakUndoCommand remove = {}; EXPECT_EQ(oaknode_node_disconnect_undoable(dst, "float_in", &remove), OAKNODE_OK); - ASSERT_NE(remove, nullptr); + ASSERT_NE(remove.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(remove), OAKUNDO_OK); EXPECT_EQ(oaknode_node_input_is_connected(dst, "float_in", &connected), OAKNODE_OK); @@ -458,8 +458,8 @@ TEST(NodeGraphTest, ConnectDisconnectUndoable) EXPECT_EQ(oaknode_node_disconnect_undoable(dst, "int_in", &remove), OAKNODE_E_NOT_FOUND); - oakundo_command_free(remove); - oakundo_command_free(add); + oakundo_command_free(&remove); + oakundo_command_free(&add); } TEST(NodeLinkTest, LinkUnlinkLiveAndUndoable) @@ -492,16 +492,16 @@ TEST(NodeLinkTest, LinkUnlinkLiveAndUndoable) EXPECT_EQ(oaknode_node_are_linked(a, b, &linked), OAKNODE_OK); EXPECT_EQ(linked, 0); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_link_undoable(a, b, 1, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_are_linked(a, b, &linked), OAKNODE_OK); EXPECT_EQ(linked, 1); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_are_linked(a, b, &linked), OAKNODE_OK); EXPECT_EQ(linked, 0); - oakundo_command_free(command); + oakundo_command_free(&command); EXPECT_EQ(oaknode_node_link(nullptr, b, &done), OAKNODE_E_INVALID); } @@ -534,11 +534,11 @@ TEST(NodeContextTest, PositionsLiveAndUndoable) EXPECT_EQ(entry, ctx); EXPECT_EQ(oaknode_node_context_node_at(n, 1, &entry), OAKNODE_E_NOT_FOUND); - OakUndoCommand *command = nullptr; + OakUndoCommand command = {}; EXPECT_EQ(oaknode_node_set_context_position_undoable(n, ctx, 1.0, 2.0, 0, &command), OAKNODE_OK); - ASSERT_NE(command, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oaknode_node_get_context_position(n, ctx, &x, &y, &expanded), OAKNODE_OK); @@ -549,7 +549,7 @@ TEST(NodeContextTest, PositionsLiveAndUndoable) EXPECT_EQ(oaknode_node_get_context_position(n, ctx, &x, &y, &expanded), OAKNODE_OK); EXPECT_DOUBLE_EQ(x, 10.5); - oakundo_command_free(command); + oakundo_command_free(&command); EXPECT_EQ(oaknode_node_remove_from_context(n, ctx), OAKNODE_OK); EXPECT_EQ(oaknode_node_remove_from_context(n, ctx), OAKNODE_E_NOT_FOUND); diff --git a/src/task/c_api/project.cpp b/src/task/c_api/project.cpp index a58a63366..c73c221d2 100644 --- a/src/task/c_api/project.cpp +++ b/src/task/c_api/project.cpp @@ -118,11 +118,11 @@ OakTaskTask *oaktask_create_project_import(OakNodeFolder *folder, } } -OakUndoCommand *oaktask_import_take_command(OakTaskTask *t) +OakUndoCommand oaktask_import_take_command(OakTaskTask *t) { olive::ProjectImportTask *task = import_impl(t); if (!task) { - return NULL; + return OakUndoCommand{}; } return task->take_command(); } diff --git a/src/task/src/project/import/import.cpp b/src/task/src/project/import/import.cpp index ee9001185..ea395023a 100644 --- a/src/task/src/project/import/import.cpp +++ b/src/task/src/project/import/import.cpp @@ -65,7 +65,7 @@ ProjectImportTask::ImageSequenceConfirmFn ProjectImportTask::confirm_callback_; ProjectImportTask::ProjectImportTask( OakNodeFolder *folder, OakNodeProject *project, const std::vector &filenames) - : command_(nullptr) + : command_({}) , folder_(folder) , project_(project) , filenames_(filenames) @@ -80,8 +80,8 @@ ProjectImportTask::ProjectImportTask( ProjectImportTask::~ProjectImportTask() { - if (command_) { - oakundo_command_free(command_); + if (command_.ctx) { + oakundo_command_free(&command_); } } @@ -93,7 +93,7 @@ const int &ProjectImportTask::get_file_count() const bool ProjectImportTask::run() { command_ = oakundo_command_init_multi(); - if (!command_) { + if (!command_.ctx) { set_error("Failed to create import command"); return false; } @@ -103,8 +103,8 @@ bool ProjectImportTask::run() import(folder_, filenames_, imported, command_); if (is_cancelled()) { - oakundo_command_free(command_); - command_ = nullptr; + oakundo_command_free(&command_); + command_ = OakUndoCommand{}; return false; } return true; @@ -112,7 +112,7 @@ bool ProjectImportTask::run() void ProjectImportTask::import(OakNodeFolder *folder, const std::vector &entries, - int &counter, OakUndoCommand *parent_command) + int &counter, OakUndoCommand parent_command) { std::vector mutable_entries = entries; @@ -336,11 +336,11 @@ void ProjectImportTask::validate_image_sequence( void ProjectImportTask::add_item_to_folder(OakNodeFolder *folder, OakNodeNode *item, - OakUndoCommand *command) + OakUndoCommand command) { - OakUndoCommand *child = + OakUndoCommand child = oaknode_command_create_folder_add_child(folder, item); - if (child) { + if (child.ctx) { oakundo_command_multi_add_child(command, child); } } diff --git a/src/task/src/project/import/import.h b/src/task/src/project/import/import.h index e13b1424a..5a8e2e58c 100644 --- a/src/task/src/project/import/import.h +++ b/src/task/src/project/import/import.h @@ -45,10 +45,10 @@ public: /** Take ownership of the import command. After this call the task no * longer owns (and will not free) the returned command. */ - OakUndoCommand *take_command() + OakUndoCommand take_command() { - OakUndoCommand *c = command_; - command_ = nullptr; + OakUndoCommand c = command_; + command_ = OakUndoCommand{}; return c; } @@ -87,14 +87,14 @@ protected: private: void import(OakNodeFolder *folder, const std::vector &entries, int &counter, - OakUndoCommand *parent_command); + OakUndoCommand parent_command); void validate_image_sequence(OakNodeFootage *footage, std::vector &info_list, size_t index); void add_item_to_folder(OakNodeFolder *folder, OakNodeNode *item, - OakUndoCommand *command); + OakUndoCommand command); static bool item_is_still_image_footage_only(OakNodeFootage *footage); @@ -104,7 +104,7 @@ private: static int64_t get_image_sequence_limit(const std::string &start_fn, int64_t start, bool up); - OakUndoCommand *command_; + OakUndoCommand command_; OakNodeFolder *folder_; diff --git a/src/task/src/project/loadotio/loadotio.cpp b/src/task/src/project/loadotio/loadotio.cpp index b5d4990a5..c6da224e3 100644 --- a/src/task/src/project/loadotio/loadotio.cpp +++ b/src/task/src/project/loadotio/loadotio.cpp @@ -192,12 +192,12 @@ bool LoadOTIOTask::run() OakNodeNode *sequence_node = oaknode_sequence_as_node(sequence); oaknode_project_add_node(project_, sequence_node); - OakUndoCommand *add_seq = oaknode_command_create_folder_add_child( + OakUndoCommand add_seq = oaknode_command_create_folder_add_child( oaknode_project_root(project_), sequence_node); - if (add_seq) { + if (add_seq.ctx) { oakundo_command_redo_now(add_seq); - oakundo_command_free(add_seq); - } + oakundo_command_free(&add_seq); + } // Create a folder for this sequence's footage OakNodeFolder *sequence_footage = @@ -205,14 +205,14 @@ bool LoadOTIOTask::run() if (sequence_footage) { oaknode_node_set_label(oaknode_folder_as_node(sequence_footage), timeline->name().c_str()); - OakUndoCommand *add_folder = + OakUndoCommand add_folder = oaknode_command_create_folder_add_child( oaknode_project_root(project_), oaknode_folder_as_node(sequence_footage)); - if (add_folder) { + if (add_folder.ctx) { oakundo_command_redo_now(add_folder); - oakundo_command_free(add_folder); - } + oakundo_command_free(&add_folder); + } } // Iterate through tracks @@ -238,12 +238,12 @@ bool LoadOTIOTask::run() OakNodeTrackList *track_list = nullptr; oaknode_sequence_get_track_list(sequence, track_type, &track_list); - OakUndoCommand *add_track = + OakUndoCommand add_track = oaktimeline_add_track_command(track_list); - if (add_track) { + if (add_track.ctx) { oakundo_command_redo_now(add_track); - oakundo_command_free(add_track); - } + oakundo_command_free(&add_track); + } int count = 0; oaknode_tracklist_get_track_count(track_list, &count); @@ -403,15 +403,15 @@ bool LoadOTIOTask::run() label.c_str()); if (sequence_footage) { - OakUndoCommand *add_footage = + OakUndoCommand add_footage = oaknode_command_create_folder_add_child( sequence_footage, oaknode_footage_as_node( probed_item)); - if (add_footage) { + if (add_footage.ctx) { oakundo_command_redo_now(add_footage); - oakundo_command_free(add_footage); - } + oakundo_command_free(&add_footage); + } } } } diff --git a/src/task/tests/task_test.cpp b/src/task/tests/task_test.cpp index 5c52cd249..e7f8bddd8 100644 --- a/src/task/tests/task_test.cpp +++ b/src/task/tests/task_test.cpp @@ -164,9 +164,9 @@ TEST_F(OakTaskFixture, ImportDemoFootage) OAKTASK_E_NOT_FOUND); // The import command adds the footage to the folder; undo removes it - OakUndoCommand *cmd = oaktask_import_take_command(t); - ASSERT_NE(cmd, nullptr); - EXPECT_EQ(oaktask_import_take_command(t), nullptr); + OakUndoCommand cmd = oaktask_import_take_command(t); + ASSERT_NE(cmd.ctx, nullptr); + EXPECT_EQ(oaktask_import_take_command(t).ctx, nullptr); EXPECT_EQ(oaknode_folder_child_count(folder), 0); // not yet redone @@ -176,7 +176,7 @@ TEST_F(OakTaskFixture, ImportDemoFootage) oakundo_command_undo_now(cmd); EXPECT_EQ(oaknode_folder_child_count(folder), 0); - oakundo_command_free(cmd); + oakundo_command_free(&cmd); oaktask_task_free(t); EXPECT_EQ(oaktask_create_project_import(nullptr, project_, urls, 1), @@ -385,11 +385,11 @@ TEST(OakTaskOTIO, SaveLoadRoundTrip) OakNodeFolder *root = oaknode_project_root(project); ASSERT_NE(root, nullptr); - OakUndoCommand *add_seq = oaknode_command_create_folder_add_child( + OakUndoCommand add_seq = oaknode_command_create_folder_add_child( root, oaknode_sequence_as_node(sequence)); - ASSERT_NE(add_seq, nullptr); + ASSERT_NE(add_seq.ctx, nullptr); oakundo_command_redo_now(add_seq); - oakundo_command_free(add_seq); + oakundo_command_free(&add_seq); std::string path = (std::filesystem::temp_directory_path() / "oaktask_otio_test.otio") diff --git a/src/timeline/c_api/edit.cpp b/src/timeline/c_api/edit.cpp index 3e36dad6a..8dcdbe808 100644 --- a/src/timeline/c_api/edit.cpp +++ b/src/timeline/c_api/edit.cpp @@ -32,16 +32,12 @@ namespace { -OakUndoCommand *wrap_command(olive::UndoCommand *command) +OakUndoCommand wrap_command(olive::UndoCommand *command) { if (!command) { - return NULL; + return OakUndoCommand{}; } - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand{command, true}; - if (!handle) { - delete command; - } - return handle; + return oakundo_capi::make_command_handle(command, true); } olive::core::Rational rat(int64_t n, int64_t d) @@ -56,73 +52,73 @@ olive::Timeline::MovementMode to_mode(int mode) } // namespace -OakUndoCommand *oaktimeline_add_track_command(OakNodeTrackList *list) +OakUndoCommand oaktimeline_add_track_command(OakNodeTrackList *list) { if (!list) { - return NULL; + return OakUndoCommand{}; } try { return wrap_command(new olive::TimelineAddTrackCommand(list)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_remove_track_command(OakNodeTrack *track) +OakUndoCommand oaktimeline_remove_track_command(OakNodeTrack *track) { if (!track) { - return NULL; + return OakUndoCommand{}; } try { return wrap_command(new olive::TimelineRemoveTrackCommand(track)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_place_block_command(OakNodeTrackList *list, +OakUndoCommand oaktimeline_place_block_command(OakNodeTrackList *list, int track_index, OakNodeBlock *block, int64_t in_num, int64_t in_den) { if (!list || !block) { - return NULL; + return OakUndoCommand{}; } try { return wrap_command(new olive::TrackPlaceBlockCommand( list, track_index, block, rat(in_num, in_den))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_replace_block_with_gap_command( +OakUndoCommand oaktimeline_replace_block_with_gap_command( OakNodeTrack *track, OakNodeBlock *block) { if (!track || !block) { - return NULL; + return OakUndoCommand{}; } try { return wrap_command( new olive::TrackReplaceBlockWithGapCommand(track, block)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_trim_command(OakNodeTrack *track, +OakUndoCommand oaktimeline_trim_command(OakNodeTrack *track, OakNodeBlock *block, int64_t new_length_num, int64_t new_length_den, int mode) { if (!track || !block || (mode != OAKTIMELINE_MOVEMENT_TRIM_IN && mode != OAKTIMELINE_MOVEMENT_TRIM_OUT)) { - return NULL; + return OakUndoCommand{}; } try { @@ -131,16 +127,16 @@ OakUndoCommand *oaktimeline_trim_command(OakNodeTrack *track, rat(new_length_num, new_length_den), to_mode(mode))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_split_command(OakNodeBlock *const *blocks, +OakUndoCommand oaktimeline_split_command(OakNodeBlock *const *blocks, int count, int64_t point_num, int64_t point_den) { if (!blocks || count <= 0) { - return NULL; + return OakUndoCommand{}; } try { @@ -153,17 +149,17 @@ OakUndoCommand *oaktimeline_split_command(OakNodeBlock *const *blocks, } return wrap_command(multi); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_split_preserving_links_command( +OakUndoCommand oaktimeline_split_preserving_links_command( OakNodeBlock *const *blocks, int count, const int64_t *point_nums, const int64_t *point_dens, int time_count) { if (!blocks || count <= 0 || !point_nums || !point_dens || time_count <= 0) { - return NULL; + return OakUndoCommand{}; } try { @@ -176,18 +172,18 @@ OakUndoCommand *oaktimeline_split_preserving_links_command( return wrap_command(new olive::BlockSplitPreservingLinksCommand( block_vec, times)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_ripple_delete_gaps_command( +OakUndoCommand oaktimeline_ripple_delete_gaps_command( OakNodeSequence *sequence, const int64_t *in_nums, const int64_t *in_dens, const int64_t *out_nums, const int64_t *out_dens, OakNodeTrack *const *tracks, int range_count) { if (!sequence || !in_nums || !in_dens || !out_nums || !out_dens || !tracks || range_count <= 0) { - return NULL; + return OakUndoCommand{}; } try { @@ -203,17 +199,17 @@ OakUndoCommand *oaktimeline_ripple_delete_gaps_command( new olive::TimelineRippleDeleteGapsAtRegionsCommand(sequence, regions)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_slide_command( +OakUndoCommand oaktimeline_slide_command( OakNodeTrack *track, OakNodeBlock *const *blocks, int block_count, OakNodeBlock *in_adjacent, OakNodeBlock *out_adjacent, int64_t movement_num, int64_t movement_den) { if (!track || !blocks || block_count <= 0) { - return NULL; + return OakUndoCommand{}; } try { @@ -222,16 +218,16 @@ OakUndoCommand *oaktimeline_slide_command( track, block_vec, in_adjacent, out_adjacent, rat(movement_num, movement_den))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_ripple_remove_area_command( +OakUndoCommand oaktimeline_ripple_remove_area_command( OakNodeTrack *track, int64_t in_num, int64_t in_den, int64_t out_num, int64_t out_den) { if (!track) { - return NULL; + return OakUndoCommand{}; } try { @@ -239,18 +235,18 @@ OakUndoCommand *oaktimeline_ripple_remove_area_command( track, olive::core::TimeRange(rat(in_num, in_den), rat(out_num, out_den)))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_insert_gaps_command(OakNodeTrackList *list, +OakUndoCommand oaktimeline_insert_gaps_command(OakNodeTrackList *list, int64_t point_num, int64_t point_den, int64_t length_num, int64_t length_den) { if (!list) { - return NULL; + return OakUndoCommand{}; } try { @@ -258,6 +254,6 @@ OakUndoCommand *oaktimeline_insert_gaps_command(OakNodeTrackList *list, new olive::TrackListInsertGaps(list, rat(point_num, point_den), rat(length_num, length_den))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } diff --git a/src/timeline/c_api/marker.cpp b/src/timeline/c_api/marker.cpp index 15b4f739f..cb02008f8 100644 --- a/src/timeline/c_api/marker.cpp +++ b/src/timeline/c_api/marker.cpp @@ -43,16 +43,12 @@ OakTimelineMarkerList *wrap(olive::TimelineMarkerList *l) return reinterpret_cast(l); } -OakUndoCommand *wrap_command(olive::UndoCommand *command) +OakUndoCommand wrap_command(olive::UndoCommand *command) { if (!command) { - return NULL; + return OakUndoCommand{}; } - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand{command, true}; - if (!handle) { - delete command; - } - return handle; + return oakundo_capi::make_command_handle(command, true); } olive::core::Rational rat(int n, int d) @@ -130,12 +126,12 @@ int oaktimeline_marker_at(const OakTimelineMarkerList *list, int index, return needed; } -OakUndoCommand *oaktimeline_marker_add_command( +OakUndoCommand oaktimeline_marker_add_command( OakTimelineMarkerList *list, int in_num, int in_den, int out_num, int out_den, const char *name, int color) { if (!list) { - return NULL; + return OakUndoCommand{}; } try { @@ -145,32 +141,32 @@ OakUndoCommand *oaktimeline_marker_add_command( rat(int(out_num), int(out_den))), name ? name : "", color)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_marker_remove_at_command( +OakUndoCommand oaktimeline_marker_remove_at_command( OakTimelineMarkerList *list, int index) { olive::TimelineMarker *m = marker_at(list, index); if (!m) { - return NULL; + return OakUndoCommand{}; } try { return wrap_command(new olive::MarkerRemoveCommand(m, impl(list))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_marker_set_time_command( +OakUndoCommand oaktimeline_marker_set_time_command( OakTimelineMarkerList *list, int index, int in_num, int in_den, int out_num, int out_den) { olive::TimelineMarker *m = marker_at(list, index); if (!m) { - return NULL; + return OakUndoCommand{}; } try { @@ -178,16 +174,16 @@ OakUndoCommand *oaktimeline_marker_set_time_command( m, olive::core::TimeRange(rat(int(in_num), int(in_den)), rat(int(out_num), int(out_den))))); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_marker_set_props_command( +OakUndoCommand oaktimeline_marker_set_props_command( OakTimelineMarkerList *list, int index, int color, const char *name) { olive::TimelineMarker *m = marker_at(list, index); if (!m || (color < 0 && !name)) { - return NULL; + return OakUndoCommand{}; } try { @@ -202,7 +198,7 @@ OakUndoCommand *oaktimeline_marker_set_props_command( } return wrap_command(new olive::MarkerChangeNameCommand(m, name)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } diff --git a/src/timeline/c_api/workarea.cpp b/src/timeline/c_api/workarea.cpp index 0f5c7048b..773f0ae0e 100644 --- a/src/timeline/c_api/workarea.cpp +++ b/src/timeline/c_api/workarea.cpp @@ -39,16 +39,12 @@ const olive::TimelineWorkArea *impl(const OakTimelineWorkArea *h) return reinterpret_cast(h); } -OakUndoCommand *wrap_command(olive::UndoCommand *command) +OakUndoCommand wrap_command(olive::UndoCommand *command) { if (!command) { - return NULL; + return OakUndoCommand{}; } - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand{command, true}; - if (!handle) { - delete command; - } - return handle; + return oakundo_capi::make_command_handle(command, true); } olive::core::Rational rat(int64_t n, int64_t d) @@ -113,13 +109,13 @@ int oaktimeline_workarea_set_range(OakTimelineWorkArea *w, int in_num, } } -OakUndoCommand *oaktimeline_workarea_set_range_command( +OakUndoCommand oaktimeline_workarea_set_range_command( OakTimelineWorkArea *w, int in_num, int in_den, int out_num, int out_den, int old_in_num, int old_in_den, int old_out_num, int old_out_den) { if (!w) { - return NULL; + return OakUndoCommand{}; } try { @@ -129,22 +125,22 @@ OakUndoCommand *oaktimeline_workarea_set_range_command( return wrap_command( new olive::WorkareaSetRangeCommand(impl(w), range, old_range)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oaktimeline_workarea_set_enabled_command( +OakUndoCommand oaktimeline_workarea_set_enabled_command( OakTimelineWorkArea *w, int enabled) { if (!w) { - return NULL; + return OakUndoCommand{}; } try { return wrap_command( new olive::WorkareaSetEnabledCommand(impl(w), enabled != 0)); } catch (...) { - return NULL; + return OakUndoCommand{}; } } diff --git a/src/timeline/src/timelineundocommon.h b/src/timeline/src/timelineundocommon.h index b527bff24..e90ca6220 100644 --- a/src/timeline/src/timelineundocommon.h +++ b/src/timeline/src/timelineundocommon.h @@ -40,29 +40,29 @@ inline bool node_can_be_removed(OakNodeBlock *b) return node_can_be_removed(oaknode_block_as_node(b)); } -inline OakUndoCommand *create_remove_command(OakNodeNode *n) +inline OakUndoCommand create_remove_command(OakNodeNode *n) { return oaknode_command_create_remove_node(n); } -inline OakUndoCommand *create_remove_command(OakNodeBlock *b) +inline OakUndoCommand create_remove_command(OakNodeBlock *b) { return oaknode_command_create_remove_node(oaknode_block_as_node(b)); } -inline OakUndoCommand *create_and_run_remove_command(OakNodeNode *n) +inline OakUndoCommand create_and_run_remove_command(OakNodeNode *n) { - OakUndoCommand *command = create_remove_command(n); + OakUndoCommand command = create_remove_command(n); oakundo_command_redo_now(command); return command; } -inline OakUndoCommand *create_and_run_remove_command(OakNodeBlock *b) +inline OakUndoCommand create_and_run_remove_command(OakNodeBlock *b) { return create_and_run_remove_command(oaknode_block_as_node(b)); } -inline void free_remove_command(OakUndoCommand *command) +inline void free_command_handle(OakUndoCommand *command) { oakundo_command_free(command); } @@ -73,35 +73,40 @@ inline void free_remove_command(OakUndoCommand *command) */ class CHandleCommandWrapper : public UndoCommand { public: - CHandleCommandWrapper(OakUndoCommand *command) + CHandleCommandWrapper(OakUndoCommand command) : command_(command) { } virtual ~CHandleCommandWrapper() override { - if (command_) { - oakundo_command_free(command_); + if (command_.ctx) { + oakundo_command_free(&command_); } } + bool is_valid() const + { + return command_.ctx != nullptr; + } + protected: virtual void redo() override { - if (command_) { + if (command_.ctx) { oakundo_command_redo_now(command_); } } virtual void undo() override { - if (command_) { + if (command_.ctx) { oakundo_command_undo_now(command_); } } private: - OakUndoCommand *command_; + OakUndoCommand command_; }; } diff --git a/src/timeline/src/timelineundogeneral.cpp b/src/timeline/src/timelineundogeneral.cpp index e8542b581..dbec7469c 100644 --- a/src/timeline/src/timelineundogeneral.cpp +++ b/src/timeline/src/timelineundogeneral.cpp @@ -45,9 +45,9 @@ const char *k_merge_blend_input = "blend_in"; const char *k_math_param_a_input = "param_a_in"; const char *k_math_param_b_input = "param_b_in"; -void add_multi_child(OakUndoCommand *multi, OakUndoCommand *child) +void add_multi_child(OakUndoCommand multi, OakUndoCommand child) { - if (multi && child) { + if (multi.ctx && child.ctx) { oakundo_command_multi_add_child(multi, child); } } @@ -116,7 +116,7 @@ TimelineAddTrackCommand::TimelineAddTrackCommand( : timeline_(timeline) , track_(nullptr) , merge_(nullptr) - , position_command_(nullptr) + , position_command_({}) , automerge_tracks_(automerge_tracks) , track_orphaned_(false) , merge_orphaned_(false) @@ -164,8 +164,8 @@ TimelineAddTrackCommand::TimelineAddTrackCommand( TimelineAddTrackCommand::~TimelineAddTrackCommand() { - if (position_command_) { - oakundo_command_free(position_command_); + if (position_command_.ctx) { + oakundo_command_free(&position_command_); } if (track_orphaned_ && track_) { oaknode_track_free(track_); @@ -221,7 +221,7 @@ void TimelineAddTrackCommand::redo() position_factor = -position_factor; } bool create_pos_command = - (!position_command_ && (type == OAKNODE_TRACK_TYPE_VIDEO || + (!position_command_.ctx && (type == OAKNODE_TRACK_TYPE_VIDEO || type == OAKNODE_TRACK_TYPE_AUDIO)); if (create_pos_command) { position_command_ = oakundo_command_init_multi(); @@ -250,19 +250,19 @@ void TimelineAddTrackCommand::redo() } oaknode_node_connect(track_node, merge_, blend_input_.c_str()); - if (create_pos_command && position_command_) { + if (create_pos_command && position_command_.ctx) { double sx = 0, sy = 0; oaknode_node_get_context_position(sequence_node, sequence_node, &sx, &sy, NULL); - OakUndoCommand *child = nullptr; + OakUndoCommand child = {}; if (oaknode_node_set_context_position_undoable( track_node, sequence_node, sx - 1, sy - position_factor, 0, &child) == OAKNODE_OK) { add_multi_child(position_command_, child); } - child = nullptr; + child = OakUndoCommand{}; if (oaknode_node_set_context_position_undoable( merge_, sequence_node, sx, sy, 0, &child) == OAKNODE_OK) { add_multi_child(position_command_, child); @@ -285,14 +285,14 @@ void TimelineAddTrackCommand::redo() oaknode_node_connect(track_node, sequence_node, direct_input_.c_str()); - if (create_pos_command && position_command_) { + if (create_pos_command && position_command_.ctx) { // Just position directly next to the context node double sx = 0, sy = 0; oaknode_node_get_context_position(sequence_node, sequence_node, &sx, &sy, NULL); - OakUndoCommand *child = nullptr; + OakUndoCommand child = {}; if (oaknode_node_set_context_position_undoable( track_node, sequence_node, sx - 1, sy + position_factor, 0, @@ -304,14 +304,14 @@ void TimelineAddTrackCommand::redo() } // Run position command if we created one - if (position_command_) { + if (position_command_.ctx) { oakundo_command_redo_now(position_command_); } } void TimelineAddTrackCommand::undo() { - if (position_command_) { + if (position_command_.ctx) { oakundo_command_undo_now(position_command_); } @@ -371,8 +371,8 @@ void TimelineAddTrackCommand::undo() // TransitionRemoveCommand::~TransitionRemoveCommand() { - if (remove_command_) { - free_remove_command(remove_command_); + if (remove_command_.ctx) { + free_command_handle(&remove_command_); } } @@ -409,7 +409,7 @@ void TransitionRemoveCommand::redo() oaknode_track_ripple_remove_block(track_, block_); if (remove_from_graph_) { - if (!remove_command_) { + if (!remove_command_.ctx) { remove_command_ = create_remove_command(block_); } @@ -789,8 +789,8 @@ void TrackReplaceBlockWithGapCommand::create_remove_transition_command_if_necess // TimelineRemoveTrackCommand::~TimelineRemoveTrackCommand() { - if (remove_command_) { - free_remove_command(remove_command_); + if (remove_command_.ctx) { + free_command_handle(&remove_command_); } } @@ -996,7 +996,7 @@ void TimelineAddDefaultTransitionCommand::add_transition( new TrackInsertBlockAfterCommand(t, transition, insert_after)); // Connect - OakUndoCommand *edge_command = nullptr; + OakUndoCommand edge_command = {}; switch (mode) { case k_in: if (oaknode_node_connect_undoable( @@ -1015,7 +1015,7 @@ void TimelineAddDefaultTransitionCommand::add_transition( } /* fall through */ case k_out: - edge_command = nullptr; + edge_command = OakUndoCommand{}; if (oaknode_node_connect_undoable( oaknode_block_as_node(c), p, OAKNODE_TRANSITION_OUT_BLOCK_INPUT, diff --git a/src/timeline/src/timelineundogeneral.h b/src/timeline/src/timelineundogeneral.h index 5a2f0ef97..ac5b7ecfb 100644 --- a/src/timeline/src/timelineundogeneral.h +++ b/src/timeline/src/timelineundogeneral.h @@ -135,7 +135,7 @@ private: std::string direct_input_; - OakUndoCommand *position_command_; + OakUndoCommand position_command_; bool automerge_tracks_; @@ -151,7 +151,7 @@ public: : track_(track) , list_(nullptr) , index_(0) - , remove_command_(nullptr) + , remove_command_({}) { } @@ -171,7 +171,7 @@ private: int index_; - OakUndoCommand *remove_command_; + OakUndoCommand remove_command_; }; class TransitionRemoveCommand : public UndoCommand { @@ -182,7 +182,7 @@ public: , out_block_(nullptr) , in_block_(nullptr) , remove_from_graph_(remove_from_graph) - , remove_command_(nullptr) + , remove_command_({}) { } @@ -202,7 +202,7 @@ private: OakNodeBlock *in_block_; bool remove_from_graph_; - OakUndoCommand *remove_command_; + OakUndoCommand remove_command_; }; class TrackReplaceBlockWithGapCommand : public UndoCommand { diff --git a/src/timeline/src/timelineundopointer.cpp b/src/timeline/src/timelineundopointer.cpp index a80b3cc70..462cbbf3a 100644 --- a/src/timeline/src/timelineundopointer.cpp +++ b/src/timeline/src/timelineundopointer.cpp @@ -33,8 +33,8 @@ namespace olive // BlockTrimCommand::~BlockTrimCommand() { - if (deleted_adjacent_command_) { - free_remove_command(deleted_adjacent_command_); + if (deleted_adjacent_command_.ctx) { + free_command_handle(&deleted_adjacent_command_); } if (adjacent_orphaned_ && adjacent_) { oaknode_block_free(adjacent_); @@ -68,7 +68,7 @@ void BlockTrimCommand::redo() // It no longer inputs/outputs anything, remove it if (remove_block_from_graph_ && node_can_be_removed(adjacent_)) { - if (!deleted_adjacent_command_) { + if (!deleted_adjacent_command_.ctx) { deleted_adjacent_command_ = create_and_run_remove_command(adjacent_); } else { @@ -102,7 +102,7 @@ void BlockTrimCommand::undo() adjacent_orphaned_ = true; } else { if (we_removed_adjacent_) { - if (deleted_adjacent_command_) { + if (deleted_adjacent_command_.ctx) { // We deleted adjacent, restore it now oakundo_command_undo_now(deleted_adjacent_command_); } @@ -188,11 +188,11 @@ void BlockTrimCommand::prepare() // TrackSlideCommand::~TrackSlideCommand() { - if (in_adjacent_remove_command_) { - free_remove_command(in_adjacent_remove_command_); + if (in_adjacent_remove_command_.ctx) { + free_command_handle(&in_adjacent_remove_command_); } - if (out_adjacent_remove_command_) { - free_remove_command(out_adjacent_remove_command_); + if (out_adjacent_remove_command_.ctx) { + free_command_handle(&out_adjacent_remove_command_); } if (in_adjacent_orphaned_ && in_adjacent_) { oaknode_block_free(in_adjacent_); @@ -216,7 +216,7 @@ void TrackSlideCommand::redo() oaknode_track_ripple_remove_block(track_, in_adjacent_); if (node_can_be_removed(in_adjacent_)) { - if (!in_adjacent_remove_command_) { + if (!in_adjacent_remove_command_.ctx) { in_adjacent_remove_command_ = create_remove_command(in_adjacent_); } @@ -244,7 +244,7 @@ void TrackSlideCommand::redo() oaknode_track_ripple_remove_block(track_, out_adjacent_); if (node_can_be_removed(out_adjacent_)) { - if (!out_adjacent_remove_command_) { + if (!out_adjacent_remove_command_.ctx) { out_adjacent_remove_command_ = create_remove_command(out_adjacent_); } @@ -270,7 +270,7 @@ void TrackSlideCommand::undo() block_remove_from_graph(in_adjacent_, track_); in_adjacent_orphaned_ = true; } else if (we_removed_in_adjacent_) { - if (in_adjacent_remove_command_) { + if (in_adjacent_remove_command_.ctx) { // We removed this, so we can restore it now oakundo_command_undo_now(in_adjacent_remove_command_); } @@ -290,7 +290,7 @@ void TrackSlideCommand::undo() block_remove_from_graph(out_adjacent_, track_); out_adjacent_orphaned_ = true; } else if (we_removed_out_adjacent_) { - if (out_adjacent_remove_command_) { + if (out_adjacent_remove_command_.ctx) { oakundo_command_undo_now(out_adjacent_remove_command_); } diff --git a/src/timeline/src/timelineundopointer.h b/src/timeline/src/timelineundopointer.h index a723c1751..6b879f528 100644 --- a/src/timeline/src/timelineundopointer.h +++ b/src/timeline/src/timelineundopointer.h @@ -56,7 +56,7 @@ public: , block_(block) , new_length_(new_length) , mode_(mode) - , deleted_adjacent_command_(nullptr) + , deleted_adjacent_command_({}) , trim_is_a_roll_edit_(false) , remove_block_from_graph_(true) , doing_nothing_(false) @@ -109,7 +109,7 @@ private: bool needs_adjacent_; bool we_created_adjacent_; bool we_removed_adjacent_; - OakUndoCommand *deleted_adjacent_command_; + OakUndoCommand deleted_adjacent_command_; bool trim_is_a_roll_edit_; bool remove_block_from_graph_; @@ -130,12 +130,12 @@ public: , we_created_in_adjacent_(false) , we_removed_in_adjacent_(false) , in_adjacent_(in_adjacent) - , in_adjacent_remove_command_(nullptr) + , in_adjacent_remove_command_({}) , in_adjacent_orphaned_(false) , we_created_out_adjacent_(false) , we_removed_out_adjacent_(false) , out_adjacent_(out_adjacent) - , out_adjacent_remove_command_(nullptr) + , out_adjacent_remove_command_({}) , out_adjacent_orphaned_(false) { } @@ -157,12 +157,12 @@ private: bool we_created_in_adjacent_; bool we_removed_in_adjacent_; OakNodeBlock *in_adjacent_; - OakUndoCommand *in_adjacent_remove_command_; + OakUndoCommand in_adjacent_remove_command_; bool in_adjacent_orphaned_; bool we_created_out_adjacent_; bool we_removed_out_adjacent_; OakNodeBlock *out_adjacent_; - OakUndoCommand *out_adjacent_remove_command_; + OakUndoCommand out_adjacent_remove_command_; bool out_adjacent_orphaned_; }; diff --git a/src/timeline/src/timelineundoripple.cpp b/src/timeline/src/timelineundoripple.cpp index 081eac00e..1028a822b 100644 --- a/src/timeline/src/timelineundoripple.cpp +++ b/src/timeline/src/timelineundoripple.cpp @@ -49,8 +49,8 @@ TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand( TrackRippleRemoveAreaCommand::~TrackRippleRemoveAreaCommand() { delete splice_split_command_; - for (OakUndoCommand *c : remove_block_commands_) { - free_remove_command(c); + for (OakUndoCommand &c : remove_block_commands_) { + free_command_handle(&c); } } @@ -170,7 +170,7 @@ void TrackRippleRemoveAreaCommand::redo() } } - for (OakUndoCommand *c : remove_block_commands_) { + for (OakUndoCommand &c : remove_block_commands_) { oakundo_command_redo_now(c); } } diff --git a/src/timeline/src/timelineundoripple.h b/src/timeline/src/timelineundoripple.h index e6eda82b1..433bee909 100644 --- a/src/timeline/src/timelineundoripple.h +++ b/src/timeline/src/timelineundoripple.h @@ -106,7 +106,7 @@ private: bool allow_splitting_gaps_; BlockSplitCommand *splice_split_command_; - std::vector remove_block_commands_; + std::vector remove_block_commands_; }; class TrackListRippleRemoveAreaCommand : public UndoCommand { diff --git a/src/timeline/src/timelineundosplit.cpp b/src/timeline/src/timelineundosplit.cpp index f5e8e9914..15609991b 100644 --- a/src/timeline/src/timelineundosplit.cpp +++ b/src/timeline/src/timelineundosplit.cpp @@ -83,8 +83,8 @@ private: // BlockSplitCommand::~BlockSplitCommand() { - if (reconnect_tree_command_) { - oakundo_command_free(reconnect_tree_command_); + if (reconnect_tree_command_.ctx) { + oakundo_command_free(&reconnect_tree_command_); } } @@ -110,7 +110,7 @@ void BlockSplitCommand::redo() assert(point_ > block_in && point_ < block_out); - if (reconnect_tree_command_) { + if (reconnect_tree_command_.ctx) { oakundo_command_redo_now(reconnect_tree_command_); } @@ -193,7 +193,7 @@ void BlockSplitCommand::undo() oaknode_track_ripple_remove_block(track, new_block_); // If we ran a reconnect command, disconnect now - if (reconnect_tree_command_) { + if (reconnect_tree_command_.ctx) { oakundo_command_undo_now(reconnect_tree_command_); } } diff --git a/src/timeline/src/timelineundosplit.h b/src/timeline/src/timelineundosplit.h index ff3f89f3e..5d8083281 100644 --- a/src/timeline/src/timelineundosplit.h +++ b/src/timeline/src/timelineundosplit.h @@ -48,7 +48,7 @@ public: : block_(block) , new_block_(nullptr) , point_(point) - , reconnect_tree_command_(nullptr) + , reconnect_tree_command_({}) , moved_transition_(nullptr) { } @@ -77,7 +77,7 @@ private: Rational old_length_; Rational point_; - OakUndoCommand *reconnect_tree_command_; + OakUndoCommand reconnect_tree_command_; OakNodeBlock *moved_transition_; std::string moved_transition_input_; diff --git a/src/timeline/tests/timeline_test.cpp b/src/timeline/tests/timeline_test.cpp index 30c1849c0..294a32229 100644 --- a/src/timeline/tests/timeline_test.cpp +++ b/src/timeline/tests/timeline_test.cpp @@ -80,11 +80,11 @@ TEST_F(TimelineSequenceFixture, MarkerAddCountAtRemove) EXPECT_EQ(oaktimeline_marker_count(list, nullptr), OAKTIMELINE_E_INVALID); - OakUndoCommand *cmd = + OakUndoCommand cmd = oaktimeline_marker_add_command(list, 1, 2, 3, 4, "mark", 5); - ASSERT_NE(cmd, nullptr); + ASSERT_NE(cmd.ctx, nullptr); oakundo_command_redo_now(cmd); - oakundo_command_free(cmd); + oakundo_command_free(&cmd); EXPECT_EQ(oaktimeline_marker_count(list, &count), OAKTIMELINE_OK); ASSERT_EQ(count, 1); @@ -108,15 +108,15 @@ TEST_F(TimelineSequenceFixture, MarkerAddCountAtRemove) &out_d, &color, name, sizeof(name)), OAKTIMELINE_E_INVALID); - OakUndoCommand *rm = oaktimeline_marker_remove_at_command(list, 0); - ASSERT_NE(rm, nullptr); + OakUndoCommand rm = oaktimeline_marker_remove_at_command(list, 0); + ASSERT_NE(rm.ctx, nullptr); oakundo_command_redo_now(rm); - oakundo_command_free(rm); + oakundo_command_free(&rm); EXPECT_EQ(oaktimeline_marker_count(list, &count), OAKTIMELINE_OK); EXPECT_EQ(count, 0); - EXPECT_EQ(oaktimeline_marker_remove_at_command(list, 4), nullptr); + EXPECT_EQ(oaktimeline_marker_remove_at_command(list, 4).ctx, nullptr); } TEST_F(TimelineSequenceFixture, MarkerSetTimeAndPropsUndo) @@ -124,15 +124,15 @@ TEST_F(TimelineSequenceFixture, MarkerSetTimeAndPropsUndo) OakTimelineMarkerList *list = oaktimeline_marker_list_of(node_); ASSERT_NE(list, nullptr); - OakUndoCommand *add = + OakUndoCommand add = oaktimeline_marker_add_command(list, 0, 1, 1, 1, "a", 1); - ASSERT_NE(add, nullptr); + ASSERT_NE(add.ctx, nullptr); oakundo_command_redo_now(add); - oakundo_command_free(add); + oakundo_command_free(&add); - OakUndoCommand *set_time = + OakUndoCommand set_time = oaktimeline_marker_set_time_command(list, 0, 10, 1, 20, 1); - ASSERT_NE(set_time, nullptr); + ASSERT_NE(set_time.ctx, nullptr); oakundo_command_redo_now(set_time); int in_n = 0; @@ -146,13 +146,13 @@ TEST_F(TimelineSequenceFixture, MarkerSetTimeAndPropsUndo) NULL, NULL, 0), 0); EXPECT_EQ(in_n, 0); - oakundo_command_free(set_time); + oakundo_command_free(&set_time); - OakUndoCommand *props = + OakUndoCommand props = oaktimeline_marker_set_props_command(list, 0, 7, "renamed"); - ASSERT_NE(props, nullptr); + ASSERT_NE(props.ctx, nullptr); oakundo_command_redo_now(props); - oakundo_command_free(props); + oakundo_command_free(&props); int color = 0; char name[32]; @@ -162,9 +162,11 @@ TEST_F(TimelineSequenceFixture, MarkerSetTimeAndPropsUndo) EXPECT_EQ(color, 7); EXPECT_STREQ(name, "renamed"); - EXPECT_EQ(oaktimeline_marker_set_props_command(list, 0, -1, NULL), + EXPECT_EQ(oaktimeline_marker_set_props_command(list, 0, -1, NULL) + .ctx, nullptr); - EXPECT_EQ(oaktimeline_marker_set_time_command(list, 9, 0, 1, 1, 1), + EXPECT_EQ(oaktimeline_marker_set_time_command(list, 9, 0, 1, 1, 1) + .ctx, nullptr); } @@ -173,11 +175,11 @@ TEST_F(TimelineSequenceFixture, MarkerListXmlRoundTrip) OakTimelineMarkerList *list = oaktimeline_marker_list_of(node_); ASSERT_NE(list, nullptr); - OakUndoCommand *add = + OakUndoCommand add = oaktimeline_marker_add_command(list, 1, 3, 2, 3, "xml", 4); - ASSERT_NE(add, nullptr); + ASSERT_NE(add.ctx, nullptr); oakundo_command_redo_now(add); - oakundo_command_free(add); + oakundo_command_free(&add); OakXmlWriter writer = oakcommon_xml_writer_init(); ASSERT_NE(writer.ctx, nullptr); @@ -265,9 +267,9 @@ TEST_F(TimelineSequenceFixture, WorkareaUndoCommands) OakTimelineWorkArea *w = oaktimeline_workarea_of(node_); ASSERT_NE(w, nullptr); - OakUndoCommand *range_cmd = oaktimeline_workarea_set_range_command( + OakUndoCommand range_cmd = oaktimeline_workarea_set_range_command( w, 1, 2, 1, 1, 0, 1, 1, 1); - ASSERT_NE(range_cmd, nullptr); + ASSERT_NE(range_cmd.ctx, nullptr); oakundo_command_redo_now(range_cmd); int in_n = 0, out_n = 0; @@ -280,11 +282,11 @@ TEST_F(TimelineSequenceFixture, WorkareaUndoCommands) EXPECT_EQ(oaktimeline_workarea_get(w, &in_n, NULL, &out_n, NULL, NULL), OAKTIMELINE_OK); EXPECT_EQ(in_n, 0); - oakundo_command_free(range_cmd); + oakundo_command_free(&range_cmd); - OakUndoCommand *enable_cmd = + OakUndoCommand enable_cmd = oaktimeline_workarea_set_enabled_command(w, 1); - ASSERT_NE(enable_cmd, nullptr); + ASSERT_NE(enable_cmd.ctx, nullptr); oakundo_command_redo_now(enable_cmd); int enabled = 0; @@ -298,12 +300,13 @@ TEST_F(TimelineSequenceFixture, WorkareaUndoCommands) &enabled), OAKTIMELINE_OK); EXPECT_EQ(enabled, 0); - oakundo_command_free(enable_cmd); + oakundo_command_free(&enable_cmd); EXPECT_EQ(oaktimeline_workarea_set_range_command(nullptr, 0, 1, 1, 1, - 0, 1, 1, 1), + 0, 1, 1, 1) + .ctx, nullptr); - EXPECT_EQ(oaktimeline_workarea_set_enabled_command(nullptr, 1), + EXPECT_EQ(oaktimeline_workarea_set_enabled_command(nullptr, 1).ctx, nullptr); } @@ -381,8 +384,8 @@ TEST_F(TimelineSequenceFixture, AddAndRemoveTrackCommands) EXPECT_EQ(oaknode_tracklist_get_track_count(list, &count), OAKNODE_OK); const int before = count; - OakUndoCommand *add = oaktimeline_add_track_command(list); - ASSERT_NE(add, nullptr); + OakUndoCommand add = oaktimeline_add_track_command(list); + ASSERT_NE(add.ctx, nullptr); oakundo_command_redo_now(add); EXPECT_EQ(oaknode_tracklist_get_track_count(list, &count), OAKNODE_OK); @@ -393,8 +396,8 @@ TEST_F(TimelineSequenceFixture, AddAndRemoveTrackCommands) OAKNODE_OK); ASSERT_NE(track, nullptr); - OakUndoCommand *rm = oaktimeline_remove_track_command(track); - ASSERT_NE(rm, nullptr); + OakUndoCommand rm = oaktimeline_remove_track_command(track); + ASSERT_NE(rm.ctx, nullptr); oakundo_command_redo_now(rm); EXPECT_EQ(oaknode_tracklist_get_track_count(list, &count), OAKNODE_OK); @@ -408,11 +411,11 @@ TEST_F(TimelineSequenceFixture, AddAndRemoveTrackCommands) EXPECT_EQ(oaknode_tracklist_get_track_count(list, &count), OAKNODE_OK); EXPECT_EQ(count, before); - oakundo_command_free(add); - oakundo_command_free(rm); + oakundo_command_free(&add); + oakundo_command_free(&rm); - EXPECT_EQ(oaktimeline_add_track_command(nullptr), nullptr); - EXPECT_EQ(oaktimeline_remove_track_command(nullptr), nullptr); + EXPECT_EQ(oaktimeline_add_track_command(nullptr).ctx, nullptr); + EXPECT_EQ(oaktimeline_remove_track_command(nullptr).ctx, nullptr); } TEST_F(TimelineSequenceFixture, PlaceTrimSplitRemoveAreaCommands) @@ -437,9 +440,9 @@ TEST_F(TimelineSequenceFixture, PlaceTrimSplitRemoveAreaCommands) oaknode_block_as_node(clip)), OAKNODE_OK); - OakUndoCommand *place = + OakUndoCommand place = oaktimeline_place_block_command(list, 0, clip, 0, 1); - ASSERT_NE(place, nullptr); + ASSERT_NE(place.ctx, nullptr); oakundo_command_redo_now(place); int block_count = 0; @@ -448,9 +451,9 @@ TEST_F(TimelineSequenceFixture, PlaceTrimSplitRemoveAreaCommands) ASSERT_EQ(block_count, 1); // Trim out to length 4 - OakUndoCommand *trim = oaktimeline_trim_command( + OakUndoCommand trim = oaktimeline_trim_command( track, clip, 4, 1, OAKTIMELINE_MOVEMENT_TRIM_OUT); - ASSERT_NE(trim, nullptr); + ASSERT_NE(trim.ctx, nullptr); oakundo_command_redo_now(trim); int n = 0, d = 0; @@ -460,13 +463,13 @@ TEST_F(TimelineSequenceFixture, PlaceTrimSplitRemoveAreaCommands) oakundo_command_undo_now(trim); EXPECT_EQ(oaknode_block_get_length(clip, &n, &d), OAKNODE_OK); EXPECT_EQ(n, 10); - oakundo_command_free(trim); + oakundo_command_free(&trim); // Split at 5: two blocks of 5 OakNodeBlock *blocks[] = { clip }; - OakUndoCommand *split = + OakUndoCommand split = oaktimeline_split_command(blocks, 1, 5, 1); - ASSERT_NE(split, nullptr); + ASSERT_NE(split.ctx, nullptr); oakundo_command_redo_now(split); EXPECT_EQ(oaknode_track_get_block_count(track, &block_count), @@ -477,12 +480,12 @@ TEST_F(TimelineSequenceFixture, PlaceTrimSplitRemoveAreaCommands) EXPECT_EQ(oaknode_track_get_block_count(track, &block_count), OAKNODE_OK); ASSERT_EQ(block_count, 1); - oakundo_command_free(split); + oakundo_command_free(&split); // Ripple remove [3..5] then undo - OakUndoCommand *remove_area = + OakUndoCommand remove_area = oaktimeline_ripple_remove_area_command(track, 3, 1, 5, 1); - ASSERT_NE(remove_area, nullptr); + ASSERT_NE(remove_area.ctx, nullptr); oakundo_command_redo_now(remove_area); // Removing [3..5] from [0..10] splices it into [0..3] + [5..10] @@ -501,19 +504,21 @@ TEST_F(TimelineSequenceFixture, PlaceTrimSplitRemoveAreaCommands) oakundo_command_undo_now(remove_area); EXPECT_EQ(oaknode_block_get_length(clip, &n, &d), OAKNODE_OK); EXPECT_EQ(n, 10); - oakundo_command_free(remove_area); + oakundo_command_free(&remove_area); oakundo_command_undo_now(place); EXPECT_EQ(oaknode_track_get_block_count(track, &block_count), OAKNODE_OK); EXPECT_EQ(block_count, 0); - oakundo_command_free(place); + oakundo_command_free(&place); - EXPECT_EQ(oaktimeline_place_block_command(nullptr, 0, clip, 0, 1), + EXPECT_EQ(oaktimeline_place_block_command(nullptr, 0, clip, 0, 1) + .ctx, nullptr); - EXPECT_EQ(oaktimeline_trim_command(track, clip, 1, 1, 99), nullptr); - EXPECT_EQ(oaktimeline_split_command(blocks, 0, 1, 1), nullptr); - EXPECT_EQ(oaktimeline_ripple_remove_area_command(nullptr, 0, 1, 1, 1), + EXPECT_EQ(oaktimeline_trim_command(track, clip, 1, 1, 99).ctx, nullptr); + EXPECT_EQ(oaktimeline_split_command(blocks, 0, 1, 1).ctx, nullptr); + EXPECT_EQ(oaktimeline_ripple_remove_area_command(nullptr, 0, 1, 1, 1) + .ctx, nullptr); } @@ -550,9 +555,9 @@ TEST_F(TimelineSequenceFixture, ReplaceWithGapCommand) OAKNODE_OK); EXPECT_EQ(oaknode_track_append_block(track, clip2), OAKNODE_OK); - OakUndoCommand *replace = + OakUndoCommand replace = oaktimeline_replace_block_with_gap_command(track, clip); - ASSERT_NE(replace, nullptr); + ASSERT_NE(replace.ctx, nullptr); oakundo_command_redo_now(replace); int block_count = 0; @@ -569,9 +574,10 @@ TEST_F(TimelineSequenceFixture, ReplaceWithGapCommand) oakundo_command_undo_now(replace); EXPECT_EQ(oaknode_track_get_block_at(track, 0, &first), OAKNODE_OK); EXPECT_EQ(first, clip); - oakundo_command_free(replace); + oakundo_command_free(&replace); - EXPECT_EQ(oaktimeline_replace_block_with_gap_command(nullptr, clip), + EXPECT_EQ(oaktimeline_replace_block_with_gap_command(nullptr, clip) + .ctx, nullptr); EXPECT_EQ(oaknode_track_ripple_remove_block(track, clip), OAKNODE_OK); @@ -582,18 +588,20 @@ TEST_F(TimelineSequenceFixture, SlideAndInsertGapsAndRippleDeleteGapsFactories) { OakNodeBlock *clip = oaknode_block_clip_create(); EXPECT_EQ(oaktimeline_slide_command(nullptr, &clip, 1, nullptr, - nullptr, 1, 1), + nullptr, 1, 1) + .ctx, nullptr); OakNodeTrackList *list = nullptr; ASSERT_EQ(oaknode_sequence_get_track_list( sequence_, OAKNODE_TRACK_TYPE_VIDEO, &list), OAKNODE_OK); - EXPECT_EQ(oaktimeline_insert_gaps_command(nullptr, 0, 1, 1, 1), + EXPECT_EQ(oaktimeline_insert_gaps_command(nullptr, 0, 1, 1, 1).ctx, nullptr); EXPECT_EQ(oaktimeline_ripple_delete_gaps_command( - sequence_, nullptr, nullptr, nullptr, nullptr, nullptr, 0), + sequence_, nullptr, nullptr, nullptr, nullptr, nullptr, 0) + .ctx, nullptr); oaknode_block_free(clip); diff --git a/src/undo/c_api/commandhandle.h b/src/undo/c_api/commandhandle.h index 3696d9ae2..92453c573 100644 --- a/src/undo/c_api/commandhandle.h +++ b/src/undo/c_api/commandhandle.h @@ -21,21 +21,100 @@ #ifndef OAK_UNDO_COMMANDHANDLE_H #define OAK_UNDO_COMMANDHANDLE_H +#include + #include "undo/undocommand.h" #include "../src/undocommand.h" /** - * @brief Internal layout of the OakUndoCommand handle, shared between the - * c_api translation units. + * @brief Internal control block behind OakUndoCommand, shared between + * the c_api translation units. * - * `owned` is true for handles created by oakundo_command_init() / - * oakundo_command_init_multi() and false for borrowed wrappers handed out - * by oakundo_command_multi_child(). + * `owns` is true for commands created through init factories (the box + * deletes the command when the count reaches zero) and false for + * references into library-owned structures (multi children, stack + * entries): releasing those only destroys the box. */ -struct OakUndoCommand { +struct OakUndoCommandBox { olive::UndoCommand *command; - bool owned; + bool owns; + std::atomic refs; + + OakUndoCommandBox(olive::UndoCommand *c, bool o) + : command(c) + , owns(o) + , refs(1) + { + } }; +namespace oakundo_capi +{ + +inline OakUndoCommand make_command_handle(olive::UndoCommand *command, + bool owns); + +inline void command_addref(void *ctx) +{ + if (ctx) { + static_cast(ctx)->refs.fetch_add(1); + } +} + +inline void command_release(void *ctx) +{ + if (!ctx) { + return; + } + OakUndoCommandBox *box = static_cast(ctx); + if (box->refs.fetch_sub(1) == 1) { + if (box->owns) { + delete box->command; + } + delete box; + } +} + +inline OakUndoCommand make_command_handle(olive::UndoCommand *command, + bool owns) +{ + OakUndoCommand handle = {}; + if (!command) { + return handle; + } + + OakUndoCommandBox *box = new (std::nothrow) OakUndoCommandBox(command, + owns); + if (!box) { + if (owns) { + delete command; + } + return handle; + } + + handle.ctx = box; + handle.addref = command_addref; + handle.release = command_release; + handle.abi_version = OAKUNDO_ABI_VERSION; + return handle; +} + +inline olive::UndoCommand *to_command(OakUndoCommand h) +{ + if (!h.ctx) { + return nullptr; + } + return static_cast(h.ctx)->command; +} + +inline void mark_container_owned(OakUndoCommand h) +{ + if (h.ctx) { + static_cast(h.ctx)->owns = false; + } +} + +} // namespace oakundo_capi + #endif // OAK_UNDO_COMMANDHANDLE_H diff --git a/src/undo/c_api/undocommand.cpp b/src/undo/c_api/undocommand.cpp index b6b6a1d56..cb6d9afd6 100644 --- a/src/undo/c_api/undocommand.cpp +++ b/src/undo/c_api/undocommand.cpp @@ -20,10 +20,17 @@ #include "undo/undocommand.h" +#include #include +#include "../src/undocommand.h" + #include "commandhandle.h" +using oakundo_capi::make_command_handle; +using oakundo_capi::mark_container_owned; +using oakundo_capi::to_command; + namespace { @@ -33,7 +40,8 @@ namespace class CallbackUndoCommand : public olive::UndoCommand { public: CallbackUndoCommand(const OakUndoCommandVtable &vtable, void *userdata) - : vtable_(vtable), userdata_(userdata) + : vtable_(vtable) + , userdata_(userdata) { } @@ -64,75 +72,66 @@ private: void *userdata_; }; -} +} // namespace -OakUndoCommand *oakundo_command_init(const OakUndoCommandVtable *vtable, - void *userdata) +OakUndoCommand oakundo_command_init(const OakUndoCommandVtable *vtable, + void *userdata) { if (!vtable) { - return NULL; + return OakUndoCommand{}; } try { - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand(); - if (!handle) { - return NULL; - } - handle->command = new CallbackUndoCommand(*vtable, userdata); - handle->owned = true; - return handle; + return make_command_handle(new CallbackUndoCommand(*vtable, userdata), true); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -OakUndoCommand *oakundo_command_init_multi(void) +OakUndoCommand oakundo_command_init_multi(void) { try { - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand(); - if (!handle) { - return NULL; - } - handle->command = new olive::MultiUndoCommand(); - handle->owned = true; - return handle; + return make_command_handle(new olive::MultiUndoCommand(), true); } catch (...) { - return NULL; + return OakUndoCommand{}; } } -int oakundo_command_multi_add_child(OakUndoCommand *multi, - OakUndoCommand *child) +int oakundo_command_multi_add_child(OakUndoCommand multi, + OakUndoCommand child) { - if (!multi || !multi->command || !child || !child->command) { + olive::UndoCommand *parent = to_command(multi); + olive::UndoCommand *child_cmd = to_command(child); + if (!parent || !child_cmd) { return OAKUNDO_E_INVALID; } olive::MultiUndoCommand *mcu = - dynamic_cast(multi->command); + dynamic_cast(parent); if (!mcu) { return OAKUNDO_E_INVALID; } try { - mcu->add_child(child->command); - // Ownership of the underlying command moved to the multi command; - // consume the wrapper. - delete child; + mcu->add_child(child_cmd); + // The multi command now owns the C++ object; the caller's box + // becomes a non-owning reference (its release no longer deletes). + mark_container_owned(child); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_command_multi_child_count(OakUndoCommand *multi, int *out_count) +int oakundo_command_multi_child_count(OakUndoCommand multi, int *out_count) { - if (!multi || !multi->command || !out_count) { + olive::UndoCommand *parent = to_command(multi); + if (!parent || !out_count) { return OAKUNDO_E_INVALID; } olive::MultiUndoCommand *mcu = - dynamic_cast(multi->command); + dynamic_cast(parent); if (!mcu) { return OAKUNDO_E_INVALID; } @@ -145,15 +144,16 @@ int oakundo_command_multi_child_count(OakUndoCommand *multi, int *out_count) } } -int oakundo_command_multi_child(OakUndoCommand *multi, int index, - OakUndoCommand **out_child) +int oakundo_command_multi_child(OakUndoCommand multi, int index, + OakUndoCommand *out_child) { - if (!multi || !multi->command || !out_child) { + olive::UndoCommand *parent = to_command(multi); + if (!parent || !out_child) { return OAKUNDO_E_INVALID; } olive::MultiUndoCommand *mcu = - dynamic_cast(multi->command); + dynamic_cast(parent); if (!mcu) { return OAKUNDO_E_INVALID; } @@ -163,41 +163,38 @@ int oakundo_command_multi_child(OakUndoCommand *multi, int index, return OAKUNDO_E_NOT_FOUND; } - OakUndoCommand *handle = new (std::nothrow) OakUndoCommand(); - if (!handle) { - return OAKUNDO_E_NOMEM; - } - handle->command = mcu->child(index); - handle->owned = false; - *out_child = handle; + // Non-owning reference; the child stays owned by the multi command + *out_child = make_command_handle(mcu->child(index), false); + return out_child->ctx ? OAKUNDO_OK : OAKUNDO_E_NOMEM; + } catch (...) { + return OAKUNDO_E_FAILED; + } +} + +int oakundo_command_redo_now(OakUndoCommand command) +{ + olive::UndoCommand *c = to_command(command); + if (!c) { + return OAKUNDO_E_INVALID; + } + + try { + c->redo_now(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_command_redo_now(OakUndoCommand *command) +int oakundo_command_undo_now(OakUndoCommand command) { - if (!command || !command->command) { + olive::UndoCommand *c = to_command(command); + if (!c) { return OAKUNDO_E_INVALID; } try { - command->command->redo_now(); - return OAKUNDO_OK; - } catch (...) { - return OAKUNDO_E_FAILED; - } -} - -int oakundo_command_undo_now(OakUndoCommand *command) -{ - if (!command || !command->command) { - return OAKUNDO_E_INVALID; - } - - try { - command->command->undo_now(); + c->undo_now(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; @@ -206,12 +203,10 @@ int oakundo_command_undo_now(OakUndoCommand *command) void oakundo_command_free(OakUndoCommand *command) { - if (!command) { + if (!command || !command->ctx) { return; } - if (command->owned) { - delete command->command; - } - delete command; + command->release(command->ctx); + command->ctx = NULL; } diff --git a/src/undo/c_api/undostack.cpp b/src/undo/c_api/undostack.cpp index 9db2c2534..dda7faeec 100644 --- a/src/undo/c_api/undostack.cpp +++ b/src/undo/c_api/undostack.cpp @@ -20,192 +20,257 @@ #include "undo/undostack.h" +#include #include #include #include "../src/undostack.h" + #include "commandhandle.h" -struct OakUndoStack { +using oakundo_capi::mark_container_owned; +using oakundo_capi::to_command; + +namespace +{ + +struct StackBox { olive::UndoStack impl; + std::atomic refs; + + StackBox() + : refs(1) + { + } }; -OakUndoStack *oakundo_undostack_init(void) +void stack_addref(void *ctx) { - try { - return new (std::nothrow) OakUndoStack(); - } catch (...) { - return NULL; + if (ctx) { + static_cast(ctx)->refs.fetch_add(1); } } +void stack_release(void *ctx) +{ + if (!ctx) { + return; + } + StackBox *box = static_cast(ctx); + if (box->refs.fetch_sub(1) == 1) { + delete box; + } +} + +olive::UndoStack *to_stack(OakUndoStack h) +{ + if (!h.ctx) { + return nullptr; + } + return &static_cast(h.ctx)->impl; +} + +} // namespace + +OakUndoStack oakundo_undostack_init(void) +{ + OakUndoStack handle = {}; + + StackBox *box = new (std::nothrow) StackBox(); + if (!box) { + return handle; + } + + handle.ctx = box; + handle.addref = stack_addref; + handle.release = stack_release; + handle.abi_version = OAKUNDO_ABI_VERSION; + return handle; +} + void oakundo_undostack_free(OakUndoStack *stack) { - delete stack; + if (!stack || !stack->ctx) { + return; + } + + stack->release(stack->ctx); + stack->ctx = NULL; } -int oakundo_undostack_push(OakUndoStack *stack, OakUndoCommand *command, +int oakundo_undostack_push(OakUndoStack stack, OakUndoCommand command, const char *name) { - if (!stack || !command || !command->command) { + olive::UndoStack *s = to_stack(stack); + olive::UndoCommand *cmd = to_command(command); + if (!s || !cmd) { return OAKUNDO_E_INVALID; } try { - olive::UndoCommand *impl = command->command; - stack->impl.push(impl, name ? name : ""); - // Ownership of the underlying command moved to the stack (or was - // deleted as an empty multi command); consume the wrapper. - delete command; + s->push(cmd, name ? name : ""); + // The stack now owns the C++ object (or deleted it as an empty + // multi command); the caller's box becomes a non-owning reference. + mark_container_owned(command); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_push_pre_executed(OakUndoStack *stack, - OakUndoCommand *command, +int oakundo_undostack_push_pre_executed(OakUndoStack stack, + OakUndoCommand command, const char *name) { - if (!stack || !command || !command->command) { + olive::UndoStack *s = to_stack(stack); + olive::UndoCommand *cmd = to_command(command); + if (!s || !cmd) { return OAKUNDO_E_INVALID; } try { - olive::UndoCommand *impl = command->command; - stack->impl.push_pre_executed(impl, name ? name : ""); - delete command; + s->push_pre_executed(cmd, name ? name : ""); + mark_container_owned(command); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_undo(OakUndoStack *stack) +int oakundo_undostack_undo(OakUndoStack stack) { - if (!stack) { + olive::UndoStack *s = to_stack(stack); + if (!s) { return OAKUNDO_E_INVALID; } try { - stack->impl.undo(); + s->undo(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_redo(OakUndoStack *stack) +int oakundo_undostack_redo(OakUndoStack stack) { - if (!stack) { + olive::UndoStack *s = to_stack(stack); + if (!s) { return OAKUNDO_E_INVALID; } try { - stack->impl.redo(); + s->redo(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_jump(OakUndoStack *stack, int64_t index) +int oakundo_undostack_jump(OakUndoStack stack, int64_t index) { - if (!stack) { + olive::UndoStack *s = to_stack(stack); + if (!s) { return OAKUNDO_E_INVALID; } try { - stack->impl.jump(index < 0 ? 0 : static_cast(index)); + s->jump(index < 0 ? 0 : static_cast(index)); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_clear(OakUndoStack *stack) +int oakundo_undostack_clear(OakUndoStack stack) { - if (!stack) { + olive::UndoStack *s = to_stack(stack); + if (!s) { return OAKUNDO_E_INVALID; } try { - stack->impl.clear(); + s->clear(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_can_undo(OakUndoStack *stack, int *out_value) +int oakundo_undostack_can_undo(OakUndoStack stack, int *out_value) { - if (!stack || !out_value) { + olive::UndoStack *s = to_stack(stack); + if (!s || !out_value) { return OAKUNDO_E_INVALID; } try { - *out_value = stack->impl.can_undo() ? 1 : 0; + *out_value = s->can_undo() ? 1 : 0; return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_can_redo(OakUndoStack *stack, int *out_value) +int oakundo_undostack_can_redo(OakUndoStack stack, int *out_value) { - if (!stack || !out_value) { + olive::UndoStack *s = to_stack(stack); + if (!s || !out_value) { return OAKUNDO_E_INVALID; } try { - *out_value = stack->impl.can_redo() ? 1 : 0; + *out_value = s->can_redo() ? 1 : 0; return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_count(OakUndoStack *stack, int64_t *out_count) +int oakundo_undostack_count(OakUndoStack stack, int64_t *out_count) { - if (!stack || !out_count) { + olive::UndoStack *s = to_stack(stack); + if (!s || !out_count) { return OAKUNDO_E_INVALID; } try { - *out_count = stack->impl.command_count(); + *out_count = s->command_count(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_index(OakUndoStack *stack, int64_t *out_index) +int oakundo_undostack_index(OakUndoStack stack, int64_t *out_index) { - if (!stack || !out_index) { + olive::UndoStack *s = to_stack(stack); + if (!s || !out_index) { return OAKUNDO_E_INVALID; } try { - *out_index = stack->impl.done_count(); + *out_index = s->done_count(); return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; } } -int oakundo_undostack_command_text(OakUndoStack *stack, int64_t row, +int oakundo_undostack_command_text(OakUndoStack stack, int64_t row, char *buf, int buf_size) { - if (!stack) { + olive::UndoStack *s = to_stack(stack); + if (!s) { return OAKUNDO_E_INVALID; } try { - if (row < 0 || row >= stack->impl.command_count()) { + if (row < 0 || row >= s->command_count()) { return OAKUNDO_E_NOT_FOUND; } - std::string name = stack->impl.command_name(int(row)); + std::string name = s->command_name(int(row)); int required = static_cast(name.size()) + 1; if (buf && buf_size > 0) { @@ -222,19 +287,20 @@ int oakundo_undostack_command_text(OakUndoStack *stack, int64_t row, } } -int oakundo_undostack_command_is_done(OakUndoStack *stack, int64_t row, +int oakundo_undostack_command_is_done(OakUndoStack stack, int64_t row, int *out_value) { - if (!stack || !out_value) { + olive::UndoStack *s = to_stack(stack); + if (!s || !out_value) { return OAKUNDO_E_INVALID; } try { - if (row < 0 || row >= stack->impl.command_count()) { + if (row < 0 || row >= s->command_count()) { return OAKUNDO_E_NOT_FOUND; } - *out_value = stack->impl.command_is_done(int(row)) ? 1 : 0; + *out_value = s->command_is_done(int(row)) ? 1 : 0; return OAKUNDO_OK; } catch (...) { return OAKUNDO_E_FAILED; diff --git a/src/undo/tests/undocommand_test.cpp b/src/undo/tests/undocommand_test.cpp index 461b5c92b..0107e0e9b 100644 --- a/src/undo/tests/undocommand_test.cpp +++ b/src/undo/tests/undocommand_test.cpp @@ -46,11 +46,11 @@ OakUndoCommandVtable make_vtable() return vtable; } -OakUndoCommand *make_command(CommandLog &log) +OakUndoCommand make_command(CommandLog &log) { OakUndoCommandVtable vtable = make_vtable(); - OakUndoCommand *command = oakundo_command_init(&vtable, &log); - EXPECT_NE(command, nullptr); + OakUndoCommand command = oakundo_command_init(&vtable, &log); + EXPECT_NE(command.ctx, nullptr); return command; } @@ -59,25 +59,25 @@ OakUndoCommand *make_command(CommandLog &log) TEST(OakUndoCommand, InitFree) { CommandLog log; - OakUndoCommand *command = make_command(log); - oakundo_command_free(command); + OakUndoCommand command = make_command(log); + oakundo_command_free(&command); EXPECT_EQ(log.free_count, 1); } TEST(OakUndoCommand, InitNullVtableFails) { - EXPECT_EQ(oakundo_command_init(nullptr, nullptr), nullptr); + EXPECT_EQ(oakundo_command_init(nullptr, nullptr).ctx, nullptr); } TEST(OakUndoCommand, FreeNullIsNoOp) { - oakundo_command_free(nullptr); + { OakUndoCommand h = {}; oakundo_command_free(&h); } } TEST(OakUndoCommand, RedoUndoNowRoundtrip) { CommandLog log; - OakUndoCommand *command = make_command(log); + OakUndoCommand command = make_command(log); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(log.redo_count, 1); @@ -91,13 +91,15 @@ TEST(OakUndoCommand, RedoUndoNowRoundtrip) EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); EXPECT_EQ(log.undo_count, 1); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(OakUndoCommand, RedoNowNullHandleFails) { - EXPECT_EQ(oakundo_command_redo_now(nullptr), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_command_undo_now(nullptr), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_command_redo_now(OakUndoCommand{}), + OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_command_undo_now(OakUndoCommand{}), + OAKUNDO_E_INVALID); } TEST(OakUndoCommand, NullCallbacksAreNoOp) @@ -107,19 +109,19 @@ TEST(OakUndoCommand, NullCallbacksAreNoOp) vtable.undo = nullptr; vtable.free_fn = nullptr; - OakUndoCommand *command = oakundo_command_init(&vtable, nullptr); - ASSERT_NE(command, nullptr); + OakUndoCommand command = oakundo_command_init(&vtable, nullptr); + ASSERT_NE(command.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(command), OAKUNDO_OK); EXPECT_EQ(oakundo_command_undo_now(command), OAKUNDO_OK); - oakundo_command_free(command); + oakundo_command_free(&command); } TEST(OakUndoCommand, MultiAddChildAndCount) { CommandLog log_a; CommandLog log_b; - OakUndoCommand *multi = oakundo_command_init_multi(); - ASSERT_NE(multi, nullptr); + OakUndoCommand multi = oakundo_command_init_multi(); + ASSERT_NE(multi.ctx, nullptr); int count = -1; EXPECT_EQ(oakundo_command_multi_child_count(multi, &count), OAKUNDO_OK); @@ -142,7 +144,7 @@ TEST(OakUndoCommand, MultiAddChildAndCount) EXPECT_EQ(log_b.undo_count, 1); // Freeing the multi frees the children it owns. - oakundo_command_free(multi); + oakundo_command_free(&multi); EXPECT_EQ(log_a.free_count, 1); EXPECT_EQ(log_b.free_count, 1); } @@ -150,43 +152,43 @@ TEST(OakUndoCommand, MultiAddChildAndCount) TEST(OakUndoCommand, MultiChildBorrowedHandle) { CommandLog log; - OakUndoCommand *multi = oakundo_command_init_multi(); - ASSERT_NE(multi, nullptr); + OakUndoCommand multi = oakundo_command_init_multi(); + ASSERT_NE(multi.ctx, nullptr); ASSERT_EQ(oakundo_command_multi_add_child(multi, make_command(log)), OAKUNDO_OK); - OakUndoCommand *child = nullptr; + OakUndoCommand child = {}; EXPECT_EQ(oakundo_command_multi_child(multi, 0, &child), OAKUNDO_OK); - ASSERT_NE(child, nullptr); + ASSERT_NE(child.ctx, nullptr); EXPECT_EQ(oakundo_command_redo_now(child), OAKUNDO_OK); EXPECT_EQ(log.redo_count, 1); // Freeing the borrowed wrapper must not free the underlying command. - oakundo_command_free(child); + oakundo_command_free(&child); EXPECT_EQ(log.free_count, 0); - oakundo_command_free(multi); + oakundo_command_free(&multi); EXPECT_EQ(log.free_count, 1); } TEST(OakUndoCommand, MultiErrorPaths) { CommandLog log; - OakUndoCommand *multi = oakundo_command_init_multi(); - ASSERT_NE(multi, nullptr); - OakUndoCommand *plain = make_command(log); + OakUndoCommand multi = oakundo_command_init_multi(); + ASSERT_NE(multi.ctx, nullptr); + OakUndoCommand plain = make_command(log); int count = 0; - OakUndoCommand *child = nullptr; + OakUndoCommand child = {}; - EXPECT_EQ(oakundo_command_multi_add_child(nullptr, plain), + EXPECT_EQ(oakundo_command_multi_add_child(OakUndoCommand{}, plain), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_command_multi_add_child(multi, nullptr), + EXPECT_EQ(oakundo_command_multi_add_child(multi, OakUndoCommand{}), OAKUNDO_E_INVALID); // plain is not a multi command EXPECT_EQ(oakundo_command_multi_add_child(plain, make_command(log)), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_command_multi_child_count(nullptr, &count), + EXPECT_EQ(oakundo_command_multi_child_count(OakUndoCommand{}, &count), OAKUNDO_E_INVALID); EXPECT_EQ(oakundo_command_multi_child_count(multi, nullptr), OAKUNDO_E_INVALID); @@ -197,6 +199,6 @@ TEST(OakUndoCommand, MultiErrorPaths) EXPECT_EQ(oakundo_command_multi_child(multi, -1, &child), OAKUNDO_E_NOT_FOUND); - oakundo_command_free(multi); - oakundo_command_free(plain); + oakundo_command_free(&multi); + oakundo_command_free(&plain); } diff --git a/src/undo/tests/undostack_test.cpp b/src/undo/tests/undostack_test.cpp index 7bcc27ecf..7ac4ccd15 100644 --- a/src/undo/tests/undostack_test.cpp +++ b/src/undo/tests/undostack_test.cpp @@ -53,36 +53,36 @@ OakUndoCommandVtable make_vtable() return vtable; } -OakUndoCommand *make_command(CommandLog &log) +OakUndoCommand make_command(CommandLog &log) { OakUndoCommandVtable vtable = make_vtable(); - OakUndoCommand *command = oakundo_command_init(&vtable, &log); - EXPECT_NE(command, nullptr); + OakUndoCommand command = oakundo_command_init(&vtable, &log); + EXPECT_NE(command.ctx, nullptr); return command; } -int64_t stack_index(OakUndoStack *stack) +int64_t stack_index(OakUndoStack stack) { int64_t index = -1; EXPECT_EQ(oakundo_undostack_index(stack, &index), OAKUNDO_OK); return index; } -int64_t stack_count(OakUndoStack *stack) +int64_t stack_count(OakUndoStack stack) { int64_t count = -1; EXPECT_EQ(oakundo_undostack_count(stack, &count), OAKUNDO_OK); return count; } -int stack_can_undo(OakUndoStack *stack) +int stack_can_undo(OakUndoStack stack) { int value = -1; EXPECT_EQ(oakundo_undostack_can_undo(stack, &value), OAKUNDO_OK); return value; } -int stack_can_redo(OakUndoStack *stack) +int stack_can_redo(OakUndoStack stack) { int value = -1; EXPECT_EQ(oakundo_undostack_can_redo(stack, &value), OAKUNDO_OK); @@ -93,8 +93,8 @@ int stack_can_redo(OakUndoStack *stack) TEST(OakUndoStack, InitFree) { - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); // A fresh stack holds the "New/Open Project" empty command. EXPECT_EQ(stack_count(stack), 1); @@ -102,12 +102,12 @@ TEST(OakUndoStack, InitFree) EXPECT_EQ(stack_can_undo(stack), 0); EXPECT_EQ(stack_can_redo(stack), 0); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, FreeNullIsNoOp) { - oakundo_undostack_free(nullptr); + { OakUndoStack h = {}; oakundo_undostack_free(&h); } } TEST(OakUndoStack, NullHandleFails) @@ -117,39 +117,39 @@ TEST(OakUndoStack, NullHandleFails) int value = 0; char buf[8]; - EXPECT_EQ(oakundo_undostack_push(nullptr, make_command(log), "x"), + EXPECT_EQ(oakundo_undostack_push(OakUndoStack{}, make_command(log), "x"), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_push_pre_executed(nullptr, make_command(log), + EXPECT_EQ(oakundo_undostack_push_pre_executed(OakUndoStack{}, make_command(log), "x"), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_undo(nullptr), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_redo(nullptr), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_jump(nullptr, 0), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_clear(nullptr), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_can_undo(nullptr, &value), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_can_redo(nullptr, &value), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_count(nullptr, &value64), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_index(nullptr, &value64), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_command_text(nullptr, 0, buf, sizeof(buf)), + EXPECT_EQ(oakundo_undostack_undo(OakUndoStack{}), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_redo(OakUndoStack{}), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_jump(OakUndoStack{}, 0), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_clear(OakUndoStack{}), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_can_undo(OakUndoStack{}, &value), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_can_redo(OakUndoStack{}, &value), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_count(OakUndoStack{}, &value64), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_index(OakUndoStack{}, &value64), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_command_text(OakUndoStack{}, 0, buf, sizeof(buf)), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_command_is_done(nullptr, 0, &value), + EXPECT_EQ(oakundo_undostack_command_is_done(OakUndoStack{}, 0, &value), OAKUNDO_E_INVALID); - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); - EXPECT_EQ(oakundo_undostack_push(stack, nullptr, "x"), OAKUNDO_E_INVALID); - EXPECT_EQ(oakundo_undostack_push_pre_executed(stack, nullptr, "x"), + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); + EXPECT_EQ(oakundo_undostack_push(stack, OakUndoCommand{}, "x"), OAKUNDO_E_INVALID); + EXPECT_EQ(oakundo_undostack_push_pre_executed(stack, OakUndoCommand{}, "x"), OAKUNDO_E_INVALID); EXPECT_EQ(oakundo_undostack_can_undo(stack, nullptr), OAKUNDO_E_INVALID); EXPECT_EQ(oakundo_undostack_count(stack, nullptr), OAKUNDO_E_INVALID); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, PushUndoRedoRoundtrip) { CommandLog log; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push(stack, make_command(log), "edit"), OAKUNDO_OK); @@ -171,7 +171,7 @@ TEST(OakUndoStack, PushUndoRedoRoundtrip) EXPECT_EQ(stack_can_undo(stack), 1); EXPECT_EQ(stack_can_redo(stack), 0); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); EXPECT_EQ(log.free_count, 1); } @@ -179,8 +179,8 @@ TEST(OakUndoStack, PushClearsRedoable) { CommandLog log_a; CommandLog log_b; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push(stack, make_command(log_a), "a"), OAKUNDO_OK); @@ -194,14 +194,14 @@ TEST(OakUndoStack, PushClearsRedoable) EXPECT_EQ(stack_can_redo(stack), 0); EXPECT_EQ(stack_count(stack), 2); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, PushPreExecutedSkipsRedo) { CommandLog log; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push_pre_executed(stack, make_command(log), "done elsewhere"), @@ -215,33 +215,33 @@ TEST(OakUndoStack, PushPreExecutedSkipsRedo) ASSERT_EQ(oakundo_undostack_redo(stack), OAKUNDO_OK); EXPECT_EQ(log.redo_count, 1); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, EmptyMultiIsDiscarded) { - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); - OakUndoCommand *multi = oakundo_command_init_multi(); - ASSERT_NE(multi, nullptr); + OakUndoCommand multi = oakundo_command_init_multi(); + ASSERT_NE(multi.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push(stack, multi, "empty"), OAKUNDO_OK); EXPECT_EQ(stack_count(stack), 1); multi = oakundo_command_init_multi(); - ASSERT_NE(multi, nullptr); + ASSERT_NE(multi.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push_pre_executed(stack, multi, "empty"), OAKUNDO_OK); EXPECT_EQ(stack_count(stack), 1); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, Jump) { CommandLog logs[3]; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); for (int i = 0; i < 3; i++) { char name[8]; @@ -271,14 +271,14 @@ TEST(OakUndoStack, Jump) ASSERT_EQ(oakundo_undostack_jump(stack, -5), OAKUNDO_OK); EXPECT_EQ(stack_index(stack), 1); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, Clear) { CommandLog log; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push(stack, make_command(log), "edit"), OAKUNDO_OK); @@ -294,14 +294,14 @@ TEST(OakUndoStack, Clear) int(strlen("New/Open Project")) + 1); EXPECT_STREQ(buf, "New/Open Project"); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, CommandTextTwoStage) { CommandLog log; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push(stack, make_command(log), "hello"), OAKUNDO_OK); @@ -333,14 +333,14 @@ TEST(OakUndoStack, CommandTextTwoStage) OAKUNDO_OK); EXPECT_EQ(oakundo_undostack_command_text(stack, 2, nullptr, 0), 1); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, CommandIsDone) { CommandLog log; - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); ASSERT_EQ(oakundo_undostack_push(stack, make_command(log), "edit"), OAKUNDO_OK); @@ -359,13 +359,13 @@ TEST(OakUndoStack, CommandIsDone) EXPECT_EQ(oakundo_undostack_command_is_done(stack, 0, nullptr), OAKUNDO_E_INVALID); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } TEST(OakUndoStack, MaxUndoCommands) { - OakUndoStack *stack = oakundo_undostack_init(); - ASSERT_NE(stack, nullptr); + OakUndoStack stack = oakundo_undostack_init(); + ASSERT_NE(stack.ctx, nullptr); // The fresh stack holds 1 empty command; push well past the 200 cap. std::vector> logs; @@ -397,7 +397,7 @@ TEST(OakUndoStack, MaxUndoCommands) EXPECT_EQ(stack_can_undo(stack), 0); EXPECT_EQ(stack_count(stack), 200); - oakundo_undostack_free(stack); + oakundo_undostack_free(&stack); } namespace