blob: 449bcbc032e43fb1f95ca6402aff0c727d15ce4a [file] [log] [blame]
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001#include "aos/flatbuffers/static_flatbuffers.h"
2
Stephan Pleines6191f1d2024-05-30 20:44:45 -07003#include <stddef.h>
4#include <stdint.h>
5
6#include <algorithm>
7#include <map>
8#include <optional>
9#include <ostream>
10#include <utility>
11#include <vector>
12
13#include "absl/strings/numbers.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070014#include "absl/strings/str_cat.h"
15#include "absl/strings/str_format.h"
16#include "absl/strings/str_join.h"
17#include "absl/strings/str_replace.h"
Austin Schuhf8440852024-05-31 10:46:50 -070018#include "absl/strings/substitute.h"
Stephan Pleines6191f1d2024-05-30 20:44:45 -070019#include "flatbuffers/base.h"
20#include "flatbuffers/string.h"
21#include "flatbuffers/vector.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070022#include "glog/logging.h"
23
Stephan Pleines6191f1d2024-05-30 20:44:45 -070024#include "aos/flatbuffers/base.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070025#include "aos/json_to_flatbuffer.h"
Stephan Pleines6191f1d2024-05-30 20:44:45 -070026
James Kuszmaulf5eb4682023-09-22 17:16:59 -070027namespace aos::fbs {
28namespace {
29// Represents a given field within a type with all of the data that we actually
30// care about.
31struct FieldData {
32 // Field name.
33 std::string name;
34 // Whether it is an inline data type (scalar/struct vs vector/table/string).
35 bool is_inline = true;
Austin Schuhf8440852024-05-31 10:46:50 -070036 // Whether the elements are inline (vector of ints vs vector of strings).
37 bool elements_are_inline = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -070038 // Whether this is a struct or not.
39 bool is_struct = false;
James Kuszmaul6be41022023-12-20 11:55:28 -080040 // Whether this is a repeated type (vector or string).
41 bool is_repeated = false;
James Kuszmaulf5eb4682023-09-22 17:16:59 -070042 // Full C++ type of this field.
43 std::string full_type = "";
44 // Full flatbuffer type for this field.
45 // Only specified for Tables.
46 std::optional<std::string> fbs_type = std::nullopt;
47 // Size of this field in the inline field data (i.e., size of the field for
48 // is_inline fields; 4 bytes for the offset for vectors/tables/strings).
49 size_t inline_size = 0u;
50 // Alignment of the inline data.
51 size_t inline_alignment = 0u;
52 // vtable offset of the field.
53 size_t vtable_offset = 0u;
Austin Schuhf8440852024-05-31 10:46:50 -070054 // Size of the elements in the vector, if this is a vector.
55 size_t element_size = 0u;
James Kuszmaulf5eb4682023-09-22 17:16:59 -070056};
57
58const reflection::Object *GetObject(const reflection::Schema *schema,
59 const int index) {
60 return (index == -1) ? schema->root_table() : schema->objects()->Get(index);
61}
62
63// Returns the flatbuffer field attribute with the specified name, if available.
64std::optional<std::string_view> GetAttribute(const reflection::Field *field,
65 std::string_view attribute) {
66 if (!field->has_attributes()) {
67 return std::nullopt;
68 }
69 const reflection::KeyValue *kv =
70 field->attributes()->LookupByKey(attribute.data());
71 if (kv == nullptr) {
72 return std::nullopt;
73 }
74 return kv->value()->string_view();
75}
76
77// Returns the implied value of an attribute that specifies a length (i.e., 0 if
78// the attribute is not specified; the integer value otherwise).
79int64_t GetLengthAttributeOrZero(const reflection::Field *field,
80 std::string_view attribute) {
81 std::optional<std::string_view> str = GetAttribute(field, attribute);
82 if (!str.has_value()) {
83 return 0;
84 }
85 int64_t value;
86 CHECK(absl::SimpleAtoi(str.value(), &value))
87 << ": Field " << field->name()->string_view()
88 << " must specify a positive integer for the " << attribute
89 << " attribute. Got \"" << str.value() << "\".";
90 CHECK_LE(0, value) << ": Field " << field->name()->string_view()
91 << " must have a non-negative " << attribute << ".";
92 return value;
93}
94
95const std::string ScalarCppType(const reflection::BaseType type) {
96 switch (type) {
97 case reflection::BaseType::Bool:
98 return "bool";
99 case reflection::BaseType::Byte:
100 return "int8_t";
101 case reflection::BaseType::UByte:
102 return "uint8_t";
103 case reflection::BaseType::Short:
104 return "int16_t";
105 case reflection::BaseType::UShort:
106 return "uint16_t";
107 case reflection::BaseType::Int:
108 return "int32_t";
109 case reflection::BaseType::UInt:
110 return "uint32_t";
111 case reflection::BaseType::Long:
112 return "int64_t";
113 case reflection::BaseType::ULong:
114 return "uint64_t";
115 case reflection::BaseType::Float:
116 return "float";
117 case reflection::BaseType::Double:
118 return "double";
119 case reflection::BaseType::UType:
120 case reflection::BaseType::String:
121 case reflection::BaseType::Vector:
122 case reflection::BaseType::Obj:
123 case reflection::BaseType::None:
124 case reflection::BaseType::Union:
125 case reflection::BaseType::Array:
126 case reflection::BaseType::MaxBaseType:
127 LOG(FATAL) << ": Type " << reflection::EnumNameBaseType(type)
128 << " not a scalar.";
129 }
130 LOG(FATAL) << "Unreachable";
131}
132
133const std::string FlatbufferNameToCppName(const std::string_view input) {
134 return absl::StrReplaceAll(input, {{".", "::"}});
135}
136
137const std::string AosNameForRawFlatbuffer(const std::string_view base_name) {
138 return absl::StrCat(base_name, "Static");
139}
140
141const std::string IncludePathForFbs(
142 std::string_view fbs_file, std::string_view include_suffix = "static") {
James Kuszmaul1e57af92023-12-20 15:34:54 -0800143 // Special case for the reflection_generated.h, which is checked into the
144 // repo.
145 // Note that we *do* autogenerated the reflection_static.h but that because
146 // it uses a special import path, we end up overriding the include anyways
147 // (note that we could muck around with the paths on the bazel side to instead
148 // get a cc_library with the correct include paths specified, although it is
149 // not clear that that would be any simpler than the extra else-if).
150 if (fbs_file == "reflection/reflection.fbs") {
151 if (include_suffix == "generated") {
152 return "flatbuffers/reflection_generated.h";
153 } else if (include_suffix == "static") {
154 return "aos/flatbuffers/reflection/reflection_static.h";
155 } else {
156 LOG(FATAL) << "This should be unreachable.";
157 }
158 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700159 fbs_file.remove_suffix(4);
160 return absl::StrCat(fbs_file, "_", include_suffix, ".h");
161}
162
163std::string ScalarOrEnumType(const reflection::Schema *schema,
164 const reflection::BaseType type, int index) {
165 return (index < 0) ? ScalarCppType(type)
166 : FlatbufferNameToCppName(
167 schema->enums()->Get(index)->name()->string_view());
168}
169
170void PopulateTypeData(const reflection::Schema *schema,
171 const reflection::Field *field_fbs, FieldData *field) {
172 VLOG(1) << aos::FlatbufferToJson(field_fbs);
173 const reflection::Type *type = field_fbs->type();
174 field->inline_size = type->base_size();
175 field->inline_alignment = type->base_size();
Austin Schuhf8440852024-05-31 10:46:50 -0700176 field->element_size = type->element_size();
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700177 switch (type->base_type()) {
178 case reflection::BaseType::Bool:
179 case reflection::BaseType::Byte:
180 case reflection::BaseType::UByte:
181 case reflection::BaseType::Short:
182 case reflection::BaseType::UShort:
183 case reflection::BaseType::Int:
184 case reflection::BaseType::UInt:
185 case reflection::BaseType::Long:
186 case reflection::BaseType::ULong:
187 case reflection::BaseType::Float:
188 case reflection::BaseType::Double:
189 // We have a scalar field, so things are relatively
190 // straightforwards.
191 field->is_inline = true;
Austin Schuhf8440852024-05-31 10:46:50 -0700192 field->elements_are_inline = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700193 field->is_struct = false;
James Kuszmaul6be41022023-12-20 11:55:28 -0800194 field->is_repeated = false;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700195 field->full_type =
196 ScalarOrEnumType(schema, type->base_type(), type->index());
197 return;
198 case reflection::BaseType::String: {
199 field->is_inline = false;
Austin Schuhf8440852024-05-31 10:46:50 -0700200 field->elements_are_inline = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700201 field->is_struct = false;
James Kuszmaul6be41022023-12-20 11:55:28 -0800202 field->is_repeated = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700203 field->full_type =
204 absl::StrFormat("::aos::fbs::String<%d>",
205 GetLengthAttributeOrZero(field_fbs, "static_length"));
206 return;
207 }
208 case reflection::BaseType::Vector: {
209 // We need to extract the name of the elements of the vector.
210 std::string element_type;
211 bool elements_are_inline = true;
James Kuszmaul6be41022023-12-20 11:55:28 -0800212 field->is_repeated = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700213 if (type->base_type() == reflection::BaseType::Vector) {
214 switch (type->element()) {
215 case reflection::BaseType::Obj: {
216 const reflection::Object *element_object =
217 GetObject(schema, type->index());
218 element_type =
219 FlatbufferNameToCppName(element_object->name()->string_view());
220 elements_are_inline = element_object->is_struct();
221 if (!element_object->is_struct()) {
222 element_type = AosNameForRawFlatbuffer(element_type);
223 field->fbs_type = element_object->name()->string_view();
224 }
225 break;
226 }
227 case reflection::BaseType::String:
228 element_type =
229 absl::StrFormat("::aos::fbs::String<%d>",
230 GetLengthAttributeOrZero(
231 field_fbs, "static_vector_string_length"));
232 elements_are_inline = false;
233 break;
234 case reflection::BaseType::Vector:
235 LOG(FATAL) << "Vectors of vectors do not exist in flatbuffers.";
236 default:
237 element_type =
238 ScalarOrEnumType(schema, type->element(), type->index());
239 };
240 }
241 field->is_inline = false;
Austin Schuhf8440852024-05-31 10:46:50 -0700242 field->elements_are_inline = elements_are_inline;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700243 field->is_struct = false;
244 field->full_type =
245 absl::StrFormat("::aos::fbs::Vector<%s, %d, %s, %s>", element_type,
246 GetLengthAttributeOrZero(field_fbs, "static_length"),
247 elements_are_inline ? "true" : "false",
248 GetAttribute(field_fbs, "force_align").value_or("0"));
249 return;
250 }
251 case reflection::BaseType::Obj: {
252 const reflection::Object *object = GetObject(schema, type->index());
253 field->is_inline = object->is_struct();
Austin Schuhf8440852024-05-31 10:46:50 -0700254 field->elements_are_inline = field->is_inline;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700255 field->is_struct = object->is_struct();
James Kuszmaul6be41022023-12-20 11:55:28 -0800256 field->is_repeated = false;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700257 const std::string flatbuffer_name =
258 FlatbufferNameToCppName(object->name()->string_view());
259 if (field->is_inline) {
260 field->full_type = flatbuffer_name;
261 field->inline_size = object->bytesize();
262 field->inline_alignment = object->minalign();
263 } else {
264 field->fbs_type = object->name()->string_view();
265 field->full_type = AosNameForRawFlatbuffer(flatbuffer_name);
266 }
267 return;
268 }
269 case reflection::BaseType::None:
270 case reflection::BaseType::UType:
271 case reflection::BaseType::Union:
272 case reflection::BaseType::Array:
273 case reflection::BaseType::MaxBaseType:
274 LOG(FATAL) << ": Type " << reflection::EnumNameBaseType(type->base_type())
275 << " not supported currently.";
276 };
277}
278
279std::string MakeMoveConstructor(std::string_view type_name) {
280 return absl::StrFormat(R"code(
281 // We need to provide a MoveConstructor to allow this table to be
282 // used inside of vectors, but we do not want it readily available to
283 // users. See TableMover for more details.
284 %s(%s &&) = default;
285 friend struct ::aos::fbs::internal::TableMover<%s>;
286 )code",
287 type_name, type_name, type_name);
288}
289
290std::string MakeConstructor(std::string_view type_name) {
291 const std::string constructor_body =
292 R"code(
293 CHECK_EQ(buffer.size(), kSize);
Austin Schuhf8440852024-05-31 10:46:50 -0700294 CHECK_EQ(0u, reinterpret_cast<size_t>(buffer.data() + kAlignOffset) % kAlign);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700295 PopulateVtable();
296)code";
297 return absl::StrFormat(R"code(
298 // Constructors for creating a flatbuffer object.
299 // Users should typically use the Builder class to create these objects,
300 // in order to allow it to populate the root table offset.
301
302 // The buffer provided to these constructors should be aligned to kAlign
303 // and kSize in length.
304 // The parent/allocator may not be nullptr.
305 %s(std::span<uint8_t> buffer, ::aos::fbs::ResizeableObject *parent) : Table(buffer, parent) {
306 %s
307 }
308 %s(std::span<uint8_t> buffer, ::aos::fbs::Allocator *allocator) : Table(buffer, allocator) {
309 %s
310 }
311 %s(std::span<uint8_t> buffer, ::std::unique_ptr<::aos::fbs::Allocator> allocator) : Table(buffer, ::std::move(allocator)) {
312 %s
313 }
314)code",
315 type_name, constructor_body, type_name,
316 constructor_body, type_name, constructor_body);
317}
318
319std::string MemberName(const FieldData &data) {
320 return absl::StrCat(data.name, "_");
321}
322
323std::string ObjectAbsoluteOffsetName(const FieldData &data) {
324 return absl::StrCat("object_absolute_offset_", data.name);
325}
326
327std::string InlineAbsoluteOffsetName(const FieldData &data) {
328 return absl::StrCat("kInlineAbsoluteOffset_", data.name);
329}
330
331// Generate the clear_* method for the requested field.
332std::string MakeClearer(const FieldData &field) {
333 std::string logical_clearer;
334 if (!field.is_inline) {
335 logical_clearer = MemberName(field) + ".reset();";
336 }
337 return absl::StrFormat(R"code(
338 // Clears the %s field. This will cause has_%s() to return false.
339 void clear_%s() {
340 %s
341 ClearField(%s, %d, %d);
342 }
343 )code",
344 field.name, field.name, field.name, logical_clearer,
345 InlineAbsoluteOffsetName(field), field.inline_size,
346 field.vtable_offset);
347}
348
349// Generate the has_* method for the requested field.
350std::string MakeHaser(const FieldData &field) {
351 return absl::StrFormat(R"code(
352 // Returns true if the %s field is set and can be accessed.
353 bool has_%s() const {
354 return AsFlatbuffer().has_%s();
355 }
356 )code",
357 field.name, field.name, field.name);
358}
359
360// Generates the accessors for fields which are stored inline in the flatbuffer
361// table (scalars, structs, and enums) .
362std::string MakeInlineAccessors(const FieldData &field,
363 const size_t inline_absolute_offset) {
Austin Schuhf8440852024-05-31 10:46:50 -0700364 constexpr size_t kVtablePointerSize = sizeof(soffset_t);
365 CHECK_EQ(
366 (inline_absolute_offset - kVtablePointerSize) % field.inline_alignment,
367 0u)
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700368 << ": Unaligned field " << field.name << " on " << field.full_type
369 << " with inline offset of " << inline_absolute_offset
Austin Schuhf8440852024-05-31 10:46:50 -0700370 << " and alignment of " << field.inline_alignment
371 << " and an alignment offset of " << kVtablePointerSize;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700372 const std::string setter =
373 absl::StrFormat(R"code(
374 // Sets the %s field, causing it to be populated if it is not already.
375 // This will populate the field even if the specified value is the default.
376 void set_%s(const %s &value) {
377 SetField<%s>(%s, %d, value);
378 }
379 )code",
380 field.name, field.name, field.full_type, field.full_type,
381 InlineAbsoluteOffsetName(field), field.vtable_offset);
382 const std::string getters = absl::StrFormat(
383 R"code(
384 // Returns the value of %s if set; nullopt otherwise.
385 std::optional<%s> %s() const {
Austin Schuhf8440852024-05-31 10:46:50 -0700386 return has_%s() ? std::make_optional(Get<%s>(%s)) : std::nullopt;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700387 }
388 // Returns a pointer to modify the %s field.
389 // The pointer may be invalidated by mutations/movements of the underlying buffer.
390 // Returns nullptr if the field is not set.
391 %s* mutable_%s() {
392 return has_%s() ? MutableGet<%s>(%s) : nullptr;
393 }
394 )code",
395 field.name, field.full_type, field.name, field.name, field.full_type,
396 InlineAbsoluteOffsetName(field), field.name, field.full_type, field.name,
397 field.name, field.full_type, InlineAbsoluteOffsetName(field));
398 const std::string clearer = MakeClearer(field);
399 return setter + getters + clearer + MakeHaser(field);
400}
401
402// Generates the accessors for fields which are not inline fields and have an
403// offset to the actual field content stored inline in the flatbuffer table.
404std::string MakeOffsetDataAccessors(const FieldData &field) {
Austin Schuhf8440852024-05-31 10:46:50 -0700405 const std::string setter = absl::Substitute(
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700406 R"code(
Austin Schuhf8440852024-05-31 10:46:50 -0700407 // Creates an empty object for the $0 field, which you can
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700408 // then populate/modify as desired.
409 // The field must not be populated yet.
Austin Schuhf8440852024-05-31 10:46:50 -0700410 $1* add_$0() {
411 CHECK(!$2.has_value());
412 constexpr size_t kVtableIndex = $3;
413 // If this object does not normally have its initial memory statically allocated,
414 // allocate it now (this is used for zero-length vectors).
415 if constexpr ($1::kPreallocatedSize == 0) {
416 const size_t object_absolute_offset = $4;
417 std::optional<std::span<uint8_t>> inserted_bytes =
418 InsertBytes(buffer().data() + object_absolute_offset, $1::kSize, ::aos::fbs::SetZero::kYes);
419 if (!inserted_bytes.has_value()) {
420 return nullptr;
421 }
422 // Undo changes to the object absolute offset that will have been made by
423 // the InsertBytes call.
424 // The InsertBytes() call normally goes through and "fixes" any offsets
425 // that will have been affected by the memory insertion. Unfortunately,
426 // if this object currently takes up zero bytes then the InsertBytes()
427 // cannot distinguish between this offset and the (identical) offsets for any other objects
428 // that may have been "sharing" this location. The effect of this logic
429 // is that the first object that gets populated at any given location will
430 // bump all other objects to later. This is fine, although it does mean
431 // that the order in which objects appear in memory may vary depending
432 // on the order in which they are constructed (if they start out sharing a start pointer).
433 $4 = object_absolute_offset;
434
435 // Construct the *Static object that we will use for managing this subtable.
436 $5.emplace(BufferForObject($4, $1::$7), this);
437 } else {
438 // Construct the *Static object that we will use for managing this subtable.
439 $5.emplace(BufferForObject($4, $1::kSize), this);
440 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700441 // Actually set the appropriate fields in the flatbuffer memory itself.
Austin Schuhf8440852024-05-31 10:46:50 -0700442 SetField<::flatbuffers::uoffset_t>($6, kVtableIndex, $4 - $6);
443 return &$5.value().t;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700444 }
445 )code",
Austin Schuhf8440852024-05-31 10:46:50 -0700446 field.name, // $0
447 field.full_type, // $1
448 MemberName(field), // $2
449 field.vtable_offset, // $3
450 ObjectAbsoluteOffsetName(field), // $4
451 MemberName(field), // $5
452 InlineAbsoluteOffsetName(field), // $6
453 (field.elements_are_inline
454 // When building vectors of inline elements, we want this object to
455 // consume as much of the memory that was allocated as possible. This
456 // lets the vector use the padding for storage, saving space. Round
457 // this down to the size of memory that the max number of elements
458 // fit in perfectly so the padding after that isn't owned.
459 ? "RoundedLength(inserted_bytes.value().size())"
460 // For vectors of elements, we need to pad out the inline portion of
461 // the vector storing offsets to the alignment of the actual elements
462 // so we can insert elements at the end without having to allocate
463 // padding. This saves space in the long run, and lets us consume
464 // the padding for offsets if needed.
465 : "kSize") // $7
466 );
467 const std::string getters = absl::Substitute(
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700468 R"code(
Austin Schuhf8440852024-05-31 10:46:50 -0700469 // Returns a pointer to the $0 field, if set. nullptr otherwise.
470 const $1* $0() const {
471 return $2.has_value() ? &$2.value().t : nullptr;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700472 }
Austin Schuhf8440852024-05-31 10:46:50 -0700473 $1* mutable_$0() {
474 return $2.has_value() ? &$2.value().t : nullptr;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700475 }
476 )code",
Austin Schuhf8440852024-05-31 10:46:50 -0700477 field.name, // $0
478 field.full_type, // $1
479 MemberName(field) // $2
480 );
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700481 return setter + getters + MakeClearer(field) + MakeHaser(field);
482}
483
484std::string MakeAccessors(const FieldData &field,
485 size_t inline_absolute_offset) {
486 return field.is_inline ? MakeInlineAccessors(field, inline_absolute_offset)
487 : MakeOffsetDataAccessors(field);
488}
489
490std::string MakeMembers(const FieldData &field,
491 std::string_view offset_data_absolute_offset,
492 size_t inline_absolute_offset) {
493 if (field.is_inline) {
494 return absl::StrFormat(
495 R"code(
496 // Offset from the start of the buffer to the inline data for the %s field.
497 static constexpr size_t %s = %d;
498 )code",
499 field.name, InlineAbsoluteOffsetName(field), inline_absolute_offset);
500 } else {
501 return absl::StrFormat(
502 R"code(
503 // Members relating to the %s field.
504 //
505 // *Static object used for managing this subtable. Will be nullopt
506 // when the field is not populated.
507 // We use the TableMover to be able to make this object moveable.
508 std::optional<::aos::fbs::internal::TableMover<%s>> %s;
509 // Offset from the start of the buffer to the start of the actual
510 // data for this field. Will be updated even when the table is not
511 // populated, so that we know where to construct it when requested.
Austin Schuhf8440852024-05-31 10:46:50 -0700512 static constexpr size_t kDefaultObjectAbsoluteOffset%s = %s;
513 size_t %s = kDefaultObjectAbsoluteOffset%s;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700514 // Offset from the start of the buffer to the offset in the inline data for
515 // this field.
516 static constexpr size_t %s = %d;
517 )code",
Austin Schuhf8440852024-05-31 10:46:50 -0700518 field.name, field.full_type, MemberName(field), field.name,
519 offset_data_absolute_offset, ObjectAbsoluteOffsetName(field),
520 field.name, InlineAbsoluteOffsetName(field), inline_absolute_offset);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700521 }
522}
523
524std::string MakeFullClearer(const std::vector<FieldData> &fields) {
525 std::vector<std::string> clearers;
526 for (const FieldData &field : fields) {
527 clearers.emplace_back(absl::StrFormat("clear_%s();", field.name));
528 }
529 return absl::StrFormat(R"code(
530 // Clears every field of the table, removing any existing state.
531 void Clear() { %s })code",
532 absl::StrJoin(clearers, "\n"));
533}
534
James Kuszmaul6be41022023-12-20 11:55:28 -0800535// Creates the FromFlatbuffer() method that copies from a flatbuffer object API
536// object (i.e., the FlatbufferT types).
537std::string MakeObjectCopier(const std::vector<FieldData> &fields) {
538 std::vector<std::string> copiers;
539 for (const FieldData &field : fields) {
540 if (field.is_struct) {
541 // Structs are stored as unique_ptr<FooStruct>
542 copiers.emplace_back(absl::StrFormat(R"code(
543 if (other.%s) {
544 set_%s(*other.%s);
545 }
546 )code",
547 field.name, field.name, field.name));
548 } else if (field.is_inline) {
549 // Inline non-struct elements are stored as FooType.
550 copiers.emplace_back(absl::StrFormat(R"code(
551 set_%s(other.%s);
552 )code",
553 field.name, field.name));
554 } else if (field.is_repeated) {
555 // strings are stored as std::string's.
556 // vectors are stored as std::vector's.
557 copiers.emplace_back(absl::StrFormat(R"code(
558 // Unconditionally copy strings/vectors, even if it will just end up
559 // being 0-length (this maintains consistency with the flatbuffer Pack()
560 // behavior).
Austin Schuh6bdcc372024-06-27 14:49:11 -0700561 {
562 %s* added_%s = add_%s();
563 CHECK(added_%s != nullptr);
564 if (!added_%s->FromFlatbuffer(other.%s)) {
James Kuszmaul6be41022023-12-20 11:55:28 -0800565 // Fail if we were unable to copy (e.g., if we tried to copy in a long
566 // vector and do not have the space for it).
567 return false;
568 }
569 }
570 )code",
Austin Schuh6bdcc372024-06-27 14:49:11 -0700571 field.full_type, field.name,
572 field.name, field.name, field.name,
573 field.name));
574 } else {
575 // Tables are stored as unique_ptr<FooTable>
576 copiers.emplace_back(absl::StrFormat(R"code(
577 if (other.%s) {
578 %s* added_%s = add_%s();
579 CHECK(added_%s != nullptr);
580 if (!added_%s->FromFlatbuffer(*other.%s)) {
581 // Fail if we were unable to copy (e.g., if we tried to copy in a long
582 // vector and do not have the space for it).
583 return false;
584 }
585 }
586 )code",
587 field.name, field.full_type,
588 field.name, field.name, field.name,
589 field.name, field.name));
James Kuszmaul6be41022023-12-20 11:55:28 -0800590 }
591 }
592 return absl::StrFormat(
593 R"code(
594 // Copies the contents of the provided flatbuffer into this flatbuffer,
595 // returning true on success.
596 // Because the Flatbuffer Object API does not provide any concept of an
597 // optionally populated scalar field, all scalar fields will be populated
598 // after a call to FromFlatbufferObject().
599 // This is a deep copy, and will call FromFlatbufferObject on
600 // any constituent objects.
601 [[nodiscard]] bool FromFlatbuffer(const Flatbuffer::NativeTableType &other) {
602 Clear();
603 %s
604 return true;
605 }
606 [[nodiscard]] bool FromFlatbuffer(const flatbuffers::unique_ptr<Flatbuffer::NativeTableType>& other) {
607 return FromFlatbuffer(*other);
608 }
609)code",
610 absl::StrJoin(copiers, "\n"));
611}
612
613// Creates the FromFlatbuffer() method that copies from an actual flatbuffer
614// object.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700615std::string MakeCopier(const std::vector<FieldData> &fields) {
616 std::vector<std::string> copiers;
617 for (const FieldData &field : fields) {
618 if (field.is_struct) {
619 copiers.emplace_back(absl::StrFormat(R"code(
James Kuszmaul692780f2023-12-20 14:01:56 -0800620 if (other.has_%s()) {
621 set_%s(*other.%s());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700622 }
623 )code",
624 field.name, field.name, field.name));
625 } else if (field.is_inline) {
626 copiers.emplace_back(absl::StrFormat(R"code(
James Kuszmaul692780f2023-12-20 14:01:56 -0800627 if (other.has_%s()) {
628 set_%s(other.%s());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700629 }
630 )code",
631 field.name, field.name, field.name));
632 } else {
633 copiers.emplace_back(absl::StrFormat(R"code(
James Kuszmaul692780f2023-12-20 14:01:56 -0800634 if (other.has_%s()) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700635 %s* added_%s = add_%s();
636 CHECK(added_%s != nullptr);
637 if (!added_%s->FromFlatbuffer(other.%s())) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700638 // Fail if we were unable to copy (e.g., if we tried to copy in a long
639 // vector and do not have the space for it).
640 return false;
641 }
642 }
643 )code",
Austin Schuh6bdcc372024-06-27 14:49:11 -0700644 field.name, field.full_type,
645 field.name, field.name, field.name,
646 field.name, field.name));
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700647 }
648 }
649 return absl::StrFormat(
650 R"code(
651 // Copies the contents of the provided flatbuffer into this flatbuffer,
652 // returning true on success.
James Kuszmaul710883b2023-12-14 14:34:48 -0800653 // This is a deep copy, and will call FromFlatbuffer on any constituent
654 // objects.
James Kuszmaul692780f2023-12-20 14:01:56 -0800655 [[nodiscard]] bool FromFlatbuffer(const Flatbuffer &other) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700656 Clear();
657 %s
658 return true;
659 }
James Kuszmaul692780f2023-12-20 14:01:56 -0800660 // Equivalent to FromFlatbuffer(const Flatbuffer&); this overload is provided
661 // to ease implementation of the aos::fbs::Vector internals.
662 [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700663 CHECK(other != nullptr);
664 return FromFlatbuffer(*other);
James Kuszmaul692780f2023-12-20 14:01:56 -0800665 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700666)code",
667 absl::StrJoin(copiers, "\n"));
668}
669
670std::string MakeSubObjectList(const std::vector<FieldData> &fields) {
671 size_t num_object_fields = 0;
672 std::vector<std::string> object_offsets;
673 std::vector<std::string> objects;
674 std::vector<std::string> inline_offsets;
675 for (const FieldData &field : fields) {
676 if (!field.is_inline) {
677 ++num_object_fields;
678 object_offsets.push_back(
679 absl::StrFormat("&%s", ObjectAbsoluteOffsetName(field)));
680 objects.push_back(absl::StrFormat("&%s->t", MemberName(field)));
681 inline_offsets.push_back(InlineAbsoluteOffsetName(field));
682 }
683 }
684 if (num_object_fields == 0) {
685 return R"code(
686 // This object has no non-inline subobjects, so we don't have to do anything special.
687 size_t NumberOfSubObjects() const final { return 0; }
688 using ::aos::fbs::ResizeableObject::SubObject;
689 SubObject GetSubObject(size_t) final { LOG(FATAL) << "No subobjects."; }
690 )code";
691 }
692 return absl::StrFormat(R"code(
693 size_t NumberOfSubObjects() const final { return %d; }
694 using ::aos::fbs::ResizeableObject::SubObject;
695 SubObject GetSubObject(size_t index) final {
696 SubObject object;
697 // Note: The below arrays are local variables rather than class members to
698 // avoid having to deal with what happens to them if the object is moved.
699
700 // Array of the members that we use for tracking where the buffers for
701 // each subobject belong.
702 // Pointers because these may need to be modified when memory is
703 // inserted into the buffer.
704 const std::array<size_t*, %d> subobject_object_offsets{%s};
705 // Actual subobjects; note that the pointers will be invalid when the
706 // field is not populated.
707 const std::array<::aos::fbs::ResizeableObject*, %d> subobject_objects{%s};
708 // Absolute offsets from the start of the buffer to where the inline
709 // entry is for each table. These offsets do not need to change at
710 // runtime (because memory is never inserted into the start of
711 // a given table), but the offsets pointed to by these offsets
712 // may need to be updated.
713 const std::array<size_t, %d> subobject_inline_offsets{%s};
714 object.inline_entry = MutableGet<::flatbuffers::uoffset_t>(subobject_inline_offsets[index]);
715 object.object = (*object.inline_entry == 0) ? nullptr : subobject_objects[index];
716 object.absolute_offset = subobject_object_offsets[index];
717 return object;
718 }
719 )code",
720 num_object_fields, num_object_fields,
721 absl::StrJoin(object_offsets, ", "), num_object_fields,
722 absl::StrJoin(objects, ", "), num_object_fields,
723 absl::StrJoin(inline_offsets, ", "));
724}
725
726std::string AlignCppString(const std::string_view expression,
Austin Schuhf8440852024-05-31 10:46:50 -0700727 const std::string_view alignment,
728 const std::string_view offset) {
729 return absl::StrCat("::aos::fbs::AlignOffset(", expression, ", ", alignment,
730 ", ", offset, ")");
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700731}
732
733std::string MakeInclude(std::string_view path, bool system = false) {
734 return absl::StrFormat("#include %s%s%s\n", system ? "<" : "\"", path,
735 system ? ">" : "\"");
736}
737
738} // namespace
739GeneratedObject GenerateCodeForObject(const reflection::Schema *schema,
740 int object_index) {
741 return GenerateCodeForObject(schema, GetObject(schema, object_index));
742}
743GeneratedObject GenerateCodeForObject(const reflection::Schema *schema,
744 const reflection::Object *object) {
745 std::vector<FieldData> fields;
746 for (const reflection::Field *field_fbs : *object->fields()) {
747 if (field_fbs->deprecated()) {
748 // Don't codegen anything for deprecated fields.
749 continue;
750 }
751 FieldData field{.name = field_fbs->name()->str(),
752 .vtable_offset = field_fbs->offset()};
753 PopulateTypeData(schema, field_fbs, &field);
754 fields.push_back(field);
755 }
Austin Schuhf8440852024-05-31 10:46:50 -0700756 std::sort(fields.begin(), fields.end(),
757 [](const FieldData &f1, const FieldData &f2) {
758 return std::make_tuple(f1.inline_alignment, f1.element_size,
759 f1.vtable_offset) >
760 std::make_tuple(f2.inline_alignment, f2.element_size,
761 f2.vtable_offset);
762 });
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700763 const size_t nominal_min_align = object->minalign();
764 std::string out_of_line_member_size = "";
765 // inline_absolute_offset tracks the current position of the inline table
766 // contents so that we can assign static offsets to each field.
Austin Schuhf8440852024-05-31 10:46:50 -0700767 constexpr size_t kVtablePointerSize = sizeof(soffset_t);
768 size_t inline_absolute_offset = kVtablePointerSize;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700769 // offset_data_relative_offset tracks the current size of the various
770 // sub-tables/vectors/strings that get stored at the end of the buffer.
771 // For simplicity, the offset data will start at a fully aligned offset
772 // (which may be larger than the soffset_t at the start of the table).
773 // Note that this is a string because it's irritating to actually pipe the
774 // numbers for size/alignment up here, so we just accumulate them here and
775 // then write the expression directly into the C++.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700776 const std::string offset_data_start_expression =
Austin Schuhf8440852024-05-31 10:46:50 -0700777 "(kVtableStart + kVtableSize)";
778 std::string offset_data_relative_offset = offset_data_start_expression;
779 std::vector<std::string> accessors;
780 std::vector<std::string> members;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700781 std::set<std::string> includes = {
782 MakeInclude("optional", true),
783 MakeInclude("aos/flatbuffers/static_table.h"),
784 MakeInclude("aos/flatbuffers/static_vector.h")};
785 for (const reflection::SchemaFile *file : *schema->fbs_files()) {
786 includes.insert(
787 MakeInclude(IncludePathForFbs(file->filename()->string_view())));
788 includes.insert(MakeInclude(
789 IncludePathForFbs(file->filename()->string_view(), "generated")));
790 for (const flatbuffers::String *included : *file->included_filenames()) {
791 includes.insert(MakeInclude(IncludePathForFbs(included->string_view())));
792 }
793 }
794 std::vector<std::string> alignments;
795 std::set<std::string> subobject_names;
796 for (const FieldData &field : fields) {
Austin Schuhf8440852024-05-31 10:46:50 -0700797 inline_absolute_offset = AlignOffset(
798 inline_absolute_offset, field.inline_alignment, kVtablePointerSize);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700799 if (!field.is_inline) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700800 alignments.push_back(field.full_type + "::kAlign");
Austin Schuhf8440852024-05-31 10:46:50 -0700801 // We care about aligning each field relative to the alignment point in
802 // this flatbuffer (which is kAlignOffset into the block of memory). We
803 // then need to report out the offset relative to the start, not the
804 // alignment point.
805 offset_data_relative_offset =
806 AlignCppString(offset_data_relative_offset + " - kAlignOffset",
807 alignments.back(),
808 field.full_type + "::kAlignOffset") +
809 " + kAlignOffset";
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700810 } else {
811 alignments.push_back(std::to_string(field.inline_alignment));
812 }
Austin Schuhf8440852024-05-31 10:46:50 -0700813 const std::string offset_data_absolute_offset = offset_data_relative_offset;
814 accessors.emplace_back(MakeAccessors(field, inline_absolute_offset));
815 members.emplace_back(MakeMembers(field, offset_data_absolute_offset,
816 inline_absolute_offset));
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700817
818 inline_absolute_offset += field.inline_size;
819 if (!field.is_inline) {
Austin Schuhf8440852024-05-31 10:46:50 -0700820 offset_data_relative_offset = absl::StrFormat(
821 "kDefaultObjectAbsoluteOffset%s + %s::kPreallocatedSize", field.name,
822 field.full_type);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700823 }
824 if (field.fbs_type.has_value()) {
825 // Is this not getting populate for the root schema?
826 subobject_names.insert(field.fbs_type.value());
827 }
828 }
829
James Kuszmaula75cd7c2023-12-07 15:52:51 -0800830 const std::string alignment = absl::StrCat(
831 "static constexpr size_t kAlign = std::max<size_t>({kMinAlign, ",
832 absl::StrJoin(alignments, ", "), "});\n");
Austin Schuhf8440852024-05-31 10:46:50 -0700833 // Same here, we want to align the end relative to the alignment point, but
834 // then we want to report out the size including the offset.
835 const std::string size = absl::StrCat(
836 "static constexpr size_t kSize = ",
837 AlignCppString(offset_data_relative_offset + " - kAlignOffset", "kAlign",
838 "kAlignOffset"),
839 " + kAlignOffset;");
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700840 const size_t inline_data_size = inline_absolute_offset;
841 const std::string constants = absl::StrFormat(
842 R"code(
Austin Schuhf8440852024-05-31 10:46:50 -0700843 // Space taken up by the inline portion of the flatbuffer table data, in
844 // bytes.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700845 static constexpr size_t kInlineDataSize = %d;
846 // Space taken up by the vtable for this object, in bytes.
Austin Schuhf8440852024-05-31 10:46:50 -0700847 static constexpr size_t kVtableSize =
848 sizeof(::flatbuffers::voffset_t) * (2 + %d);
849 // Offset from the start of the internal memory buffer to the start of the
850 // vtable.
851 static constexpr size_t kVtableStart = ::aos::fbs::AlignOffset(
852 kInlineDataSize, alignof(::flatbuffers::voffset_t));
853 // Required alignment of this object. The buffer that this object gets
854 // constructed into must be aligned to this value.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700855 %s
Austin Schuhf8440852024-05-31 10:46:50 -0700856 // Offset into this object to measure the alignment at.
857 static constexpr size_t kAlignOffset = sizeof(::flatbuffers::soffset_t);
858 static_assert(
859 %d <= kAlign,
860 "Flatbuffer schema minalign should not exceed our required alignment.");
861 // Offset from the start of the memory buffer to the start of any out-of-line
862 // data (subtables, vectors, strings).
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700863 static constexpr size_t kOffsetDataStart = %s;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700864 // Various overrides to support the Table parent class.
865 size_t FixedVtableOffset() const final { return kVtableStart; }
866 size_t VtableSize() const final { return kVtableSize; }
867 size_t InlineTableSize() const final { return kInlineDataSize; }
868 size_t OffsetDataStart() const final { return kOffsetDataStart; }
869 size_t Alignment() const final { return kAlign; }
870 // Exposes the name of the flatbuffer type to allow interchangeable use
871 // of the Flatbuffer and FlatbufferStatic types in various AOS methods.
Austin Schuhf8440852024-05-31 10:46:50 -0700872 static const char *GetFullyQualifiedName() {
873 return Flatbuffer::GetFullyQualifiedName();
874 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700875)code",
Austin Schuhf8440852024-05-31 10:46:50 -0700876 inline_data_size, object->fields()->size(), alignment, nominal_min_align,
877 offset_data_start_expression);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700878 const std::string_view fbs_type_name = object->name()->string_view();
879 const std::string type_namespace = FlatbufferNameToCppName(
880 fbs_type_name.substr(0, fbs_type_name.find_last_of(".")));
881 const std::string type_name = AosNameForRawFlatbuffer(
882 fbs_type_name.substr(fbs_type_name.find_last_of(".") + 1));
883 const std::string object_code = absl::StrFormat(
884 R"code(
885namespace %s {
886class %s : public ::aos::fbs::Table {
887 public:
888 // The underlying "raw" flatbuffer type for this type.
889 typedef %s Flatbuffer;
James Kuszmaul6be41022023-12-20 11:55:28 -0800890 typedef flatbuffers::unique_ptr<Flatbuffer::NativeTableType> FlatbufferObjectType;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700891 // Returns this object as a flatbuffer type. This reference may not be valid
892 // following mutations to the underlying flatbuffer, due to how memory may get
893 // may get moved around.
894 const Flatbuffer &AsFlatbuffer() const { return *GetFlatbuffer<Flatbuffer>(); }
895%s
896%s
897 virtual ~%s() {}
898%s
899%s
900%s
James Kuszmaul6be41022023-12-20 11:55:28 -0800901%s
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700902 private:
903%s
904%s
905%s
Austin Schuhf8440852024-05-31 10:46:50 -0700906 public:
907 // Nominal size of this object, in bytes. The object may grow beyond this
908 // size, but will always start at this size and so the initial buffer must
909 // match this size.
910 %s
911 // Always statically allocate memory for tables (set for consistency with
912 // static_vector.h).
913 static constexpr size_t kPreallocatedSize = kSize;
914 // Size required for a buffer that includes a root table offset at the start.
915 static constexpr size_t kRootSize =
916 ::aos::fbs::AlignOffset(kSize + sizeof(::flatbuffers::uoffset_t), kAlign);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700917};
918}
919 )code",
920 type_namespace, type_name, FlatbufferNameToCppName(fbs_type_name),
Austin Schuhf8440852024-05-31 10:46:50 -0700921 constants, MakeConstructor(type_name), type_name,
922 absl::StrJoin(accessors, ""), MakeFullClearer(fields), MakeCopier(fields),
923 MakeObjectCopier(fields), MakeMoveConstructor(type_name),
924 absl::StrJoin(members, ""), MakeSubObjectList(fields), size);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700925
926 GeneratedObject result;
927 result.name = fbs_type_name;
928 result.include_declarations = includes;
929 result.code = object_code;
930 result.subobjects = subobject_names;
931 return result;
932}
933
934namespace {
935
936// Generated C++ code for an entire fbs file.
937// This includes all of the actual C++ code that will be written to a file (call
938// GenerateCode() to actually get the desired contents of the file).
939struct GeneratedCode {
940 // Prefix (for include guards).
941 std::string contents_prefix;
942 // Full set of required #include declarations.
943 std::set<std::string> include_declarations;
944 // Ordered list of objects (order is necessary to ensure that any dependencies
945 // between objects are managed correctly).
946 std::vector<GeneratedObject> objects;
947 // Suffix (for include guards).
948 std::string contents_suffix;
949
950 // Combine the above things into the string that actually needs to be written
951 // to a file.
952 std::string GenerateCode() const;
953 // Combines the code for multiple objects into one.
954 static GeneratedCode MergeCode(const std::vector<GeneratedObject> &objects);
955};
956
957std::string GeneratedCode::GenerateCode() const {
958 std::string result =
959 contents_prefix + absl::StrJoin(include_declarations, "");
960 for (const auto &object : objects) {
961 result += object.code;
962 }
963 result += contents_suffix;
964 return result;
965}
966
967GeneratedCode GeneratedCode::MergeCode(
968 const std::vector<GeneratedObject> &objects) {
969 GeneratedCode result;
970 // TODO(james): Should we use #ifdef include guards instead?
971 result.contents_prefix =
972 "#pragma once\n// This is a generated file. Do not modify.\n";
973 // We need to get the ordering of objects correct in order to ensure that
974 // depended-on objects appear before their dependees.
975 // In order to do this, we:
976 // 1) Assume that any objects not in the provided vector must exist in
977 // #includes and so can be ignored.
978 // 2) Create a list of all the objects we have been provided but which we have
979 // not yet added to the results vector.
980 // 3) Until said list is empty, we iterate over it and find any object(s)
981 // which have no dependencies in the list itself, and add them to the
982 // result.
983 // We aren't going to worry about efficient graph traversal here or anything.
984 // We also don't currently attempt to support circular dependencies.
985 std::map<std::string_view, const GeneratedObject *> remaining_objects;
986 for (const auto &object : objects) {
987 remaining_objects[object.name] = &object;
988 }
989 while (!remaining_objects.empty()) {
990 std::string_view to_remove;
991 for (const auto &pair : remaining_objects) {
992 bool has_dependencies = false;
993 for (const std::string_view subobject : pair.second->subobjects) {
994 if (remaining_objects.contains(subobject)) {
995 has_dependencies = true;
996 }
997 }
998 if (has_dependencies) {
999 continue;
1000 }
1001 to_remove = pair.first;
1002 result.objects.push_back(*pair.second);
1003 result.include_declarations.insert(
1004 pair.second->include_declarations.begin(),
1005 pair.second->include_declarations.end());
1006 break;
1007 }
1008 // In order to support circular dependencies, two main things have to
1009 // change:
1010 // 1. We have to dynamically allow depopulating table members (rather than
1011 // just supporting dynamically lengthed vectors).
1012 // 2. Some of the codegen needs to be tweaked so that we can have the
1013 // generated
1014 // C++ classes depend on one another.
1015 CHECK(!to_remove.empty())
1016 << ": Circular dependencies in flatbuffers schemas are not supported.";
1017 CHECK_EQ(1u, remaining_objects.erase(to_remove))
1018 << ": Failed to remove " << to_remove;
1019 }
1020 return result;
1021}
1022} // namespace
1023
James Kuszmaul9a2d5f02023-12-14 11:38:35 -08001024std::string GenerateCodeForRootTableFile(const reflection::Schema *schema,
1025 std::string_view file_hint) {
1026 const reflection::Object *root_object = GetObject(schema, -1);
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001027 const std::string_view root_file =
James Kuszmaul9a2d5f02023-12-14 11:38:35 -08001028 (root_object == nullptr) ? file_hint
1029 : root_object->declaration_file()->string_view();
1030 std::vector<GeneratedObject> objects;
1031 if (root_object != nullptr) {
1032 objects.push_back(GenerateCodeForObject(schema, root_object));
1033 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001034 for (const reflection::Object *object : *schema->objects()) {
1035 if (object->is_struct()) {
1036 continue;
1037 }
1038 if (object->declaration_file()->string_view() == root_file) {
1039 objects.push_back(GenerateCodeForObject(schema, object));
1040 }
1041 }
1042 return GeneratedCode::MergeCode(objects).GenerateCode();
1043}
1044} // namespace aos::fbs