libcyaml icon indicating copy to clipboard operation
libcyaml copied to clipboard

Feature: pre-serialize and post-deserialize callbacks

Open alex-tee opened this issue 4 years ago • 6 comments

It seems like it would be quite common to want to do some logic on a struct before serializing it or after deserializing it. one use case would be to fill in more information on the struct after deserializing it, another would be to "prepare" some data for serialization.

the user would register callbacks on serializable structs to be called either before cyaml starts serialization or after cyaml finished deserialization

this is kinda doable if you traverse all your structs and do it manually, but it would be nice if cyaml took care of it automatically for every struct it touches

alex-tee avatar Mar 07 '20 16:03 alex-tee

Can you give me an example use-case to think about?

Why would this be more useful than simply filling out your data structures before saving, or annotating them with extra info after loading?

tlsa avatar Mar 12 '20 09:03 tlsa

i use polymorphism in my code:

typedef struct UndoableAction
{
  UndoableActionType         type;
  int                        stack_idx;
} UndoableAction;

static const cyaml_schema_field_t
  undoable_action_fields_schema[] =
{
  CYAML_FIELD_ENUM (
    "type", CYAML_FLAG_DEFAULT,
    UndoableAction, type,
    undoable_action_type_strings,
    CYAML_ARRAY_LEN (undoable_action_type_strings)),
  YAML_FIELD_INT (
    UndoableAction, stack_idx),

  CYAML_FIELD_END
};

static const cyaml_schema_value_t
  undoable_action_schema =
{
  CYAML_VALUE_MAPPING (CYAML_FLAG_POINTER,
    UndoableAction, undoable_action_fields_schema),
};
typedef struct MoveTracksAction
{
  UndoableAction  parent_instance;
  int             pos;
  TracklistSelections * tls;
} MoveTracksAction;

static const cyaml_schema_field_t
  move_tracks_action_fields_schema[] =
{
  CYAML_FIELD_MAPPING (
    "parent_instance", CYAML_FLAG_DEFAULT,
    MoveTracksAction, parent_instance,
    undoable_action_fields_schema),
  CYAML_FIELD_MAPPING_PTR (
    "tls", CYAML_FLAG_POINTER,
    MoveTracksAction, tls,
    tracklist_selections_fields_schema),
  CYAML_FIELD_INT (
    "pos", CYAML_FLAG_DEFAULT,
    MoveTracksAction, pos),

  CYAML_FIELD_END
};

static const cyaml_schema_value_t
  move_tracks_action_schema =
{
  CYAML_VALUE_MAPPING (
    CYAML_FLAG_POINTER, MoveTracksAction,
    move_tracks_action_fields_schema),
};

Then I want to serialize my Undo history which consists of many UndoableAction's, but this is impossible to serialize, so I have something like this:

typedef struct UndoStack
{
  /** Actual stack used at runtime. */
  Stack *       stack;

  /* the following are for serialization
   * purposes only */

  ArrangerSelectionsAction ** as_actions;
  size_t        num_as_actions;
  size_t        as_actions_size;

  CopyPluginsAction ** copy_plugins_actions;
  size_t        num_copy_plugins_actions;
  size_t        copy_plugins_actions_size;

  CopyTracksAction ** copy_tracks_actions;
  size_t        num_copy_tracks_actions;
  size_t        copy_tracks_actions_size;

  CreatePluginsAction ** create_plugins_actions;
  size_t        num_create_plugins_actions;
  size_t        create_plugins_actions_size;

  CreateTracksAction ** create_tracks_actions;
  size_t        num_create_tracks_actions;
  size_t        create_tracks_actions_size;

  DeletePluginsAction ** delete_plugins_actions;
  size_t        num_delete_plugins_actions;
  size_t        delete_plugins_actions_size;

  DeleteTracksAction ** delete_tracks_actions;
  size_t        num_delete_tracks_actions;
  size_t        delete_tracks_actions_size;

  EditPluginsAction ** edit_plugins_actions;
  size_t        num_edit_plugins_actions;
  size_t        edit_plugins_actions_size;

  EditTracksAction ** edit_tracks_actions;
  size_t        num_edit_tracks_actions;
  size_t        edit_tracks_actions_size;

  MovePluginsAction ** move_plugins_actions;
  size_t        num_move_plugins_actions;
  size_t        move_plugins_actions_size;

  MoveTracksAction ** move_tracks_actions;
  size_t        num_move_tracks_actions;
  size_t        move_tracks_actions_size;

} UndoStack;

static const cyaml_schema_field_t
  undo_stack_fields_schema[] =
{
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, as_actions,
    arranger_selections_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, copy_plugins_actions,
    copy_plugins_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, copy_tracks_actions,
    copy_tracks_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, create_plugins_actions,
    create_plugins_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, create_tracks_actions,
    create_tracks_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, delete_plugins_actions,
    delete_plugins_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, delete_tracks_actions,
    delete_tracks_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, edit_plugins_actions,
    edit_plugins_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, edit_tracks_actions,
    edit_tracks_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, move_plugins_actions,
    move_plugins_action_schema),
  YAML_FIELD_DYN_PTR_ARRAY_VAR_COUNT_OPTIONAL (
    UndoStack, move_tracks_actions,
    move_tracks_action_schema),

  CYAML_FIELD_END
};

static const cyaml_schema_value_t
  undo_stack_schema =
{
  YAML_VALUE_PTR (
    UndoStack, undo_stack_fields_schema),
};

(the Stack contains a bunch of void pointers to undoable actions)

So right before saving, I need to traverse the stack and fill in each array in UndoStack, so the information can be serialized properly. Then, after loading, I need to fill in the stack again from these arrays.

Why would this be more useful than simply filling out your data structures before saving, or annotating them with extra info after loading?

Convenience. My project YAML is huge and complex so it means I need to traverse the parent struct and find and do this for all child structs that need it. I also could accidentally miss something. Registering a callback for a specific type of struct would ensure that it will be called.

I also understand that this could make libcyaml more complex than it needs to be so I'm not 100% sure if this feature should be added or not.

alex-tee avatar Mar 12 '20 13:03 alex-tee

I'm involved in an open source project which needs just this. Needs to convert text to internal and reverse and enum type is not suitable.

elecpower avatar Nov 25 '21 06:11 elecpower

Would support for unions handle this use case?

struct my_thing {
        enum my_type type;
        union my_data data;
};

tlsa avatar May 15 '22 15:05 tlsa

Are you asking if supporting unions would implement this feature? I don't think so. How would unions help? For one, it may not be possible/desirable to change your structs. Also, the point of this feature request is:

  1. to add some dynamic logic for generating a value to save instead of saving something already stored in the struct
  2. to execute some other logic unrelated to saving/loading (like a notification callback)

alex-tee avatar May 15 '22 15:05 alex-tee

@alex-tee The validation callbacks on main now would effectively serve as post-deserialise callbacks.

tlsa avatar Jun 02 '23 14:06 tlsa