|
| 1 | +#include <custom_serializer.h> |
| 2 | +#include <stdio.h> |
| 3 | +#include <inttypes.h> |
| 4 | + |
| 5 | +// This example demonstrates how to create a serializer for a custom format. |
| 6 | + |
| 7 | +static int indent = 0; |
| 8 | + |
| 9 | +#define printIndented(...)\ |
| 10 | + printf("\n%*s", indent * 2, ""); printf(__VA_ARGS__) |
| 11 | + |
| 12 | +void serializeOps(ecs_world_t *world, ecs_meta_op_t *ops, |
| 13 | + int32_t op_count, const void *ptr); |
| 14 | + |
| 15 | +// Serialize a pointer of a specified type. |
| 16 | +int serialize(ecs_world_t *world, ecs_entity_t type, const void *ptr) { |
| 17 | + // The TypeSerializer component contains a vector of instructions that tells |
| 18 | + // us how to serialize the type. |
| 19 | + const EcsTypeSerializer *s = ecs_get(world, type, EcsTypeSerializer); |
| 20 | + if (!s) { |
| 21 | + printf("type does not have reflection data\n"); |
| 22 | + return -1; |
| 23 | + } |
| 24 | + |
| 25 | + serializeOps(world, ecs_vec_first(&s->ops), ecs_vec_count(&s->ops), ptr); |
| 26 | + |
| 27 | + return 0; |
| 28 | +} |
| 29 | + |
| 30 | +// Serialize contents of struct or collection scope |
| 31 | +void serializeScope(ecs_world_t *world, ecs_meta_op_t *ops, const void *ptr) { |
| 32 | + // A scope starts with a Push and ends with a Pop, so trim the first and |
| 33 | + // last instruction before forwarding. |
| 34 | + serializeOps(world, ops + 1, ops->op_count - 2, ptr); |
| 35 | +} |
| 36 | + |
| 37 | +// Serialize a struct scope |
| 38 | +void serializeStruct(ecs_world_t *world, ecs_meta_op_t *ops, |
| 39 | + const void *ptr) |
| 40 | +{ |
| 41 | + printf("{"); |
| 42 | + indent ++; |
| 43 | + |
| 44 | + serializeScope(world, ops, ptr); |
| 45 | + |
| 46 | + indent --; |
| 47 | + printIndented("}"); |
| 48 | +} |
| 49 | + |
| 50 | +// Serialize an array scope |
| 51 | +void serializeArray(ecs_world_t *world, ecs_meta_op_t *ops, |
| 52 | + int32_t elem_count, const void *ptr) |
| 53 | +{ |
| 54 | + printf("["); |
| 55 | + |
| 56 | + // Iterate elements of the array. Skip the first and last instruction |
| 57 | + // since they are PushStruct and Pop. |
| 58 | + for (int i = 0; i < elem_count; i ++) { |
| 59 | + if (i) { |
| 60 | + printf(", "); |
| 61 | + } |
| 62 | + |
| 63 | + serializeScope(world, ops, ptr); |
| 64 | + |
| 65 | + ptr = ECS_OFFSET(ptr, ops->elem_size); |
| 66 | + } |
| 67 | + |
| 68 | + printf("]"); |
| 69 | +} |
| 70 | + |
| 71 | +// Serialize a vector scope |
| 72 | +void serializeVector(ecs_world_t *world, ecs_meta_op_t *ops, |
| 73 | + const void *ptr) |
| 74 | +{ |
| 75 | + const ecs_vec_t *vec = ptr; |
| 76 | + serializeArray(world, ops, vec->count, vec->array); |
| 77 | +} |
| 78 | + |
| 79 | +// Serialize enum |
| 80 | +void serializeEnum(ecs_meta_op_t *op, const void *ptr) |
| 81 | +{ |
| 82 | + const int32_t *value = ptr; // Assume i32, but should use underlying type |
| 83 | + ecs_enum_constant_t *c = ecs_map_get_deref(op->is.constants, |
| 84 | + ecs_enum_constant_t, (ecs_map_key_t)*value); |
| 85 | + printf("%s", c->name); |
| 86 | +} |
| 87 | + |
| 88 | +// Iterate over instruction array |
| 89 | +void serializeOps(ecs_world_t *world, ecs_meta_op_t *ops, int32_t op_count, |
| 90 | + const void *base) |
| 91 | +{ |
| 92 | + for (int i = 0; i < op_count; i ++) { |
| 93 | + ecs_meta_op_t *op = &ops[i]; |
| 94 | + |
| 95 | + // Member name |
| 96 | + if (op->name) { |
| 97 | + if (i) { |
| 98 | + printf(","); |
| 99 | + } |
| 100 | + |
| 101 | + printIndented("%s: ", op->name); |
| 102 | + } |
| 103 | + |
| 104 | + // Get pointer for current field |
| 105 | + void *ptr = ECS_OFFSET(base, op->offset); |
| 106 | + |
| 107 | + switch(op->kind) { |
| 108 | + // Instructions that forward to a type scope, like a (nested) struct or |
| 109 | + // collection |
| 110 | + case EcsOpPushStruct: |
| 111 | + serializeStruct(world, op, ptr); |
| 112 | + break; |
| 113 | + case EcsOpPushArray: |
| 114 | + serializeArray(world, op, ecs_meta_op_get_elem_count(ops, ptr), ptr); |
| 115 | + break; |
| 116 | + case EcsOpPushVector: |
| 117 | + serializeVector(world, op, ptr); |
| 118 | + break; |
| 119 | + |
| 120 | + // Opaque types have in-memory representations that are opaque to |
| 121 | + // the reflection framework and cannot be serialized by just taking |
| 122 | + // a pointer + an offset. See src/addons/script/serialize.c for an |
| 123 | + // example of how to handle opaque types. |
| 124 | + case EcsOpOpaqueStruct: |
| 125 | + case EcsOpOpaqueArray: |
| 126 | + case EcsOpOpaqueVector: |
| 127 | + case EcsOpOpaqueValue: |
| 128 | + break; |
| 129 | + |
| 130 | + // Forward to type. Used for members of array/vector types. |
| 131 | + case EcsOpForward: |
| 132 | + serialize(world, op->type, ptr); |
| 133 | + break; |
| 134 | + |
| 135 | + // Serialize single values |
| 136 | + case EcsOpEnum: |
| 137 | + serializeEnum(op, ptr); |
| 138 | + break; |
| 139 | + case EcsOpBitmask: |
| 140 | + // Bitmask serialization requires iterating all the bits in a value |
| 141 | + // and looking up the corresponding constant. For an example, see |
| 142 | + // src/addons/script/serialize.c. |
| 143 | + break; |
| 144 | + case EcsOpBool: |
| 145 | + printf("%s", (*(const bool*)ptr) ? "true" : "false"); |
| 146 | + break; |
| 147 | + case EcsOpChar: |
| 148 | + printf("'%c'", *(const char*)ptr); |
| 149 | + break; |
| 150 | + case EcsOpByte: |
| 151 | + case EcsOpU8: |
| 152 | + printf("%" PRIu8, *(const uint8_t*)ptr); |
| 153 | + break; |
| 154 | + case EcsOpU16: |
| 155 | + printf("%" PRIu16, *(const uint16_t*)ptr); |
| 156 | + break; |
| 157 | + case EcsOpU32: |
| 158 | + printf("%" PRIu32, *(const uint32_t*)ptr); |
| 159 | + break; |
| 160 | + case EcsOpU64: |
| 161 | + printf("%" PRIu64, *(const uint64_t*)ptr); |
| 162 | + break; |
| 163 | + case EcsOpI8: |
| 164 | + printf("%" PRIi8, *(const int8_t*)ptr); |
| 165 | + break; |
| 166 | + case EcsOpI16: |
| 167 | + printf("%" PRIi16, *(const int16_t*)ptr); |
| 168 | + break; |
| 169 | + case EcsOpI32: |
| 170 | + printf("%" PRIi32, *(const int32_t*)ptr); |
| 171 | + break; |
| 172 | + case EcsOpI64: |
| 173 | + printf("%" PRIi64, *(const int64_t*)ptr); |
| 174 | + break; |
| 175 | + case EcsOpF32: |
| 176 | + printf("%.2f", (double)*(const float*)ptr); |
| 177 | + break; |
| 178 | + case EcsOpF64: |
| 179 | + printf("%.2f", *(const double*)ptr); |
| 180 | + break; |
| 181 | + case EcsOpUPtr: |
| 182 | + printf("%" PRIuPTR, *(const uintptr_t*)ptr); |
| 183 | + break; |
| 184 | + case EcsOpIPtr: |
| 185 | + printf("%" PRIiPTR, *(const intptr_t*)ptr); |
| 186 | + break; |
| 187 | + case EcsOpString: |
| 188 | + printf("\"%s\"", *(const char**)ptr); |
| 189 | + break; |
| 190 | + case EcsOpEntity: { |
| 191 | + char *name = ecs_get_path(world, *(ecs_entity_t*)ptr); |
| 192 | + printf("%s\n", name); |
| 193 | + ecs_os_free(name); |
| 194 | + break; |
| 195 | + } |
| 196 | + case EcsOpId: { |
| 197 | + char *name = ecs_id_str(world, *(ecs_id_t*)ptr); |
| 198 | + printf("%s\n", name); |
| 199 | + ecs_os_free(name); |
| 200 | + break; |
| 201 | + } |
| 202 | + case EcsOpPop: |
| 203 | + case EcsOpScope: |
| 204 | + case EcsOpPrimitive: |
| 205 | + // Not serializable |
| 206 | + break; |
| 207 | + } |
| 208 | + |
| 209 | + i += op->op_count - 1; // Skip over already processed instructions |
| 210 | + } |
| 211 | +} |
| 212 | + |
| 213 | +// Some types to test the serializer with |
| 214 | + |
| 215 | +typedef struct { |
| 216 | + uint8_t red; |
| 217 | + uint8_t green; |
| 218 | + uint8_t blue; |
| 219 | +} Rgb; |
| 220 | + |
| 221 | +typedef enum { |
| 222 | + Solid, Dashed |
| 223 | +} StrokeKind; |
| 224 | + |
| 225 | +typedef struct { |
| 226 | + float x, y; |
| 227 | +} Point; |
| 228 | + |
| 229 | +typedef struct { |
| 230 | + Rgb color; |
| 231 | + Rgb stroke_color; |
| 232 | + StrokeKind stroke_kind; |
| 233 | + ecs_vec_t points; |
| 234 | +} Polygon; |
| 235 | + |
| 236 | +int main(int argc, char *argv[]) { |
| 237 | + ecs_world_t *world = ecs_init_w_args(argc, argv); |
| 238 | + |
| 239 | + // We need to register reflection data for our types before we can use them |
| 240 | + // with the serializer code. We'll do it manually here, but for an easier |
| 241 | + // way to insert reflection data see the auto_* examples. |
| 242 | + |
| 243 | + ECS_COMPONENT(world, Rgb); |
| 244 | + ECS_COMPONENT(world, StrokeKind); |
| 245 | + ECS_COMPONENT(world, Point); |
| 246 | + ECS_COMPONENT(world, Polygon); |
| 247 | + |
| 248 | + ecs_struct(world, { |
| 249 | + .entity = ecs_id(Rgb), |
| 250 | + .members = { |
| 251 | + { .name = "r", .type = ecs_id(ecs_u8_t) }, |
| 252 | + { .name = "g", .type = ecs_id(ecs_u8_t) }, |
| 253 | + { .name = "b", .type = ecs_id(ecs_u8_t) }, |
| 254 | + } |
| 255 | + }); |
| 256 | + |
| 257 | + ecs_enum(world, { |
| 258 | + .entity = ecs_id(StrokeKind), |
| 259 | + .constants = { |
| 260 | + { .name = "Solid", .value = Solid }, |
| 261 | + { .name = "Dashed", .value = Dashed }, |
| 262 | + } |
| 263 | + }); |
| 264 | + |
| 265 | + ecs_struct(world, { |
| 266 | + .entity = ecs_id(Point), |
| 267 | + .members = { |
| 268 | + { .name = "x", .type = ecs_id(ecs_f32_t) }, |
| 269 | + { .name = "y", .type = ecs_id(ecs_f32_t) } |
| 270 | + } |
| 271 | + }); |
| 272 | + |
| 273 | + ecs_entity_t PointVector = ecs_vector(world, { .type = ecs_id(Point) }); |
| 274 | + |
| 275 | + ecs_struct(world, { |
| 276 | + .entity = ecs_id(Polygon), |
| 277 | + .members = { |
| 278 | + { .name = "color", .type = ecs_id(Rgb) }, |
| 279 | + { .name = "stroke_color", .type = ecs_id(Rgb) }, |
| 280 | + { .name = "stroke_kind", .type = ecs_id(StrokeKind) }, |
| 281 | + { .name = "points", .type = PointVector } |
| 282 | + } |
| 283 | + }); |
| 284 | + |
| 285 | + // Create the value |
| 286 | + ecs_vec_t points; |
| 287 | + ecs_vec_init_t(NULL, &points, Point, 0); |
| 288 | + *ecs_vec_append_t(NULL, &points, Point) = (Point){0, 0}; |
| 289 | + *ecs_vec_append_t(NULL, &points, Point) = (Point){1, 1}; |
| 290 | + *ecs_vec_append_t(NULL, &points, Point) = (Point){-1, 1}; |
| 291 | + |
| 292 | + Polygon p = { |
| 293 | + .color = {0, 0, 255}, |
| 294 | + .stroke_color = {255, 0, 0}, |
| 295 | + .stroke_kind = Dashed, |
| 296 | + .points = points |
| 297 | + }; |
| 298 | + |
| 299 | + // Serialize value |
| 300 | + serialize(world, ecs_id(Polygon), &p); |
| 301 | + printf("\n"); |
| 302 | + |
| 303 | + // Free memory |
| 304 | + ecs_vec_fini_t(NULL, &points, Point); |
| 305 | + |
| 306 | + ecs_fini(world); |
| 307 | + |
| 308 | + // Output: |
| 309 | + // { |
| 310 | + // color: { |
| 311 | + // r: 0, |
| 312 | + // g: 0, |
| 313 | + // b: 255 |
| 314 | + // }, |
| 315 | + // stroke_color: { |
| 316 | + // r: 255, |
| 317 | + // g: 0, |
| 318 | + // b: 0 |
| 319 | + // }, |
| 320 | + // stroke_kind: Dashed, |
| 321 | + // points: [{ |
| 322 | + // x: 0.00, |
| 323 | + // y: 0.00 |
| 324 | + // }, { |
| 325 | + // x: 1.00, |
| 326 | + // y: 1.00 |
| 327 | + // }, { |
| 328 | + // x: -1.00, |
| 329 | + // y: 1.00 |
| 330 | + // }] |
| 331 | + // } |
| 332 | +} |
0 commit comments