AODQ / pulchritude-engine

lingua franca game engine
MIT License
0 stars 0 forks source link

PuleDataSerializer - layout parser #44

Open AODQ opened 2 years ago

AODQ commented 2 years ago

Description

Typical file workload would currently look like in C

char const * errStr = NULL;
PuleDsValue head = ...;
MyModuleInfo info;
PULE_assert(puleDsIsObject(head));
auto widthObj = puleDsObjectMember(head, "width");
if (!widthObj || !puleDsIsI64(widthObj)) { errStr = "width"; goto FILE_FAILURE; }
info.width = puleDsAsI64(widthObj);
auto heightObj = puleDsObjectMember(head, "height");
if (!heightObj || !puleDsIsI64(heightObj)) errStr = "height"; goto FILE_FAILURE; }
info.height = puleDsAsI64(heightObj);
auto playerObj = puleDsObjectMember(head, "player");
if (!playerObj || !puleDsIsObject(playerObj)) { errStr = "player"; goto FILE_FAILURE; }
auto playerNameObj = puleDsObjectMember(playerObj, "name");
if (!playerNameObj || !puleDsIsString(playerNameObj)) { errStr = "player.name"; goto FILE_FAILURE; }
...

goto FILE_SUCCESS;
FILE_FAILURE:
*error = {
  .description = puleStringFormatDefault("error parsing file: %s", errStr),
  .id = PuleErrorMyModule_parsing,
};
return { 0 };
FILE_SUCCESS;
...

as you can see, it's a bit tedious. We're coupling error handling with parsing. You could write macros to help alleviate this pain, but other languages might not necessarily have the same sort of solution.

Instead it would be useful to have:

PuleDsLayout layout = puleDsLayout();
puleDsLayoutAddI64(layout, "width"); // assert "width" exists and its type is I64
puleDsLayoutAddI64(layout, "height");
puleDsLayoutAddI64Optional(layout, "version"); // "version" doesn't have to exist, but if it does it must be I64
puleDsLayoutPushObject(layout, "player");
  puleDsLayoutAddString(layout, "name");
puleDsLayoutPopObject(layout);
puleDsLayoutPushArray(layout, "enemies");
  puleDsLayoutAddI64(layout, "health");
  puleDsLayoutAddI64(layout, "level");
puleDsLayoutPopArray();

PuleDsValue head = ... layout;
if (puleErrorConsume(&error)) {
  return { 0 };
}

info.width = puleDsObjectMemberAsI64(head, "width");
info.height = puleDsObjectMemberAsI64(head, "height");
info.player.name = (
  puleDsObjectMemberAsString(puleDsObjectMember(head, "player")", "name")
);
auto enemies = puleDsObjectMemberAsArray(head, "enemies");
for (size_t it = 0; it < enemies.length; ++ it) {
  info.enemies.emplace_back({
    .health = puleDsObjectMemberAsI64(enemies.values[it], "health");
    .level = puleDsObjectMemberAsI64(enemies.values[it], "level");
  });
}

It's split up into two separate components. While it'd be nice to have this generate some sort of usable type, it's not really an option in C. With other languages that support introspection, this will help error integration. We can also add puleDsObjectMemberAs<Type> functions because they no longer need to be checked if valid.

Note we are electing to pick an API with

puleDsLayoutAddI64(PuleDsLayout, char const * const);
puleDsLayoutAddI64Optional(PuleDsLayout, char const * const);
... repeat for f64/string/object/array ...

instead of a single function puleDsLayoutAdd(PuleDsLayout, char const * const, PuleDsLayoutType, PuleDsLayoutIsOptional) I'm still not 100% sure if this is the right thing to do; it's less verbose on C puleDsLayoutAddObject(layout, "crate", PuleDsLayoutType_Object, PuleDsLayoutIsOptional_No); yet with other languages you could have puleDsLayout(layout, "crate", .Object, .No);`


Proposed ABI Changes

  PULE_exportFn PuleDsValue puleAssetPdsLoadFromStream(
    PuleAllocator const allocator,
    PuleStreamRead const stream,
+   PuleDsLayout const layoutOptional,
    PuleError * const error
  );

Proposed new ABI

PuleDsLayout puleDsLayout();
void puleDsLayoutAddI64(PuleDsLayout, char const * const);
void puleDsLayoutAddI64Optional(PuleDsLayout, char const * const);
void puleDsLayoutAddF64(PuleDsLayout, char const * const);
void puleDsLayoutAddF64Optional(PuleDsLayout, char const * const);
void puleDsLayoutAddString(PuleDsLayout, char const * const);
void puleDsLayoutAddStringOptional(PuleDsLayout, char const * const);
void puleDsLayoutAddArray(PuleDsLayout, char const * const);
void puleDsLayoutAddArrayOptional(PuleDsLayout, char const * const);
void puleDsLayoutAddObject(PuleDsLayout, char const * const);
void puleDsLayoutAddObjectOptional(PuleDsLayout, char const * const);

PuleDsString puleDsObjectMemberAsString(PuleDsValue, char const * const);
int64_t puleDsObjectMemberAsI64(PuleDsValue, char const * const);
float puleDsObjectMemberAsF64(PuleDsValue, char const * const);
PuleDsArray puleDsObjectMemberAsObject(PuleDsValue, char const * const);
AODQ commented 2 years ago

I realize now the puleDsObjectMemberAsObject/Array funcs should use PuleDsValueObject/Array

AODQ commented 2 years ago

possible alternative is to pass a string that has the layout ei { width: { type: "u64", }, version: { type: "string", optional: "yes" }}