Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 157 additions & 58 deletions src/idl_gen_csharp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ class CSharpGenerator : public BaseGenerator {
int length;
};

// Check if a struct (or any of its nested structs) contains array fields.
// Used to decide whether Create/Pack methods need Span overloads.
bool StructHasArrayFields(const StructDef& struct_def) const {
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
auto& field = **it;
if (IsArray(field.value.type)) return true;
if (IsStruct(field.value.type) &&
StructHasArrayFields(*field.value.type.struct_def))
return true;
}
return false;
}

public:
CSharpGenerator(const Parser& parser, const std::string& path,
const std::string& file_name)
Expand Down Expand Up @@ -536,9 +550,11 @@ class CSharpGenerator : public BaseGenerator {
}

// Recursively generate arguments for a constructor, to deal with nested
// structs.
// structs. When use_span is true, array parameters use Span<T> instead of
// T[] or T[,] for zero-allocation struct creation.
void GenStructArgs(const StructDef& struct_def, std::string* code_ptr,
const char* nameprefix, size_t array_count = 0) const {
const char* nameprefix, size_t array_count = 0,
bool use_span = false) const {
std::string& code = *code_ptr;
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
Expand All @@ -553,17 +569,21 @@ class CSharpGenerator : public BaseGenerator {
// a nested struct, prefix the name with the field name.
GenStructArgs(*field_type.struct_def, code_ptr,
(nameprefix + (EscapeKeyword(field.name) + "_")).c_str(),
array_cnt);
array_cnt, use_span);
} else {
code += ", ";
code += GenTypeBasic(type);
if (field.IsScalarOptional()) {
code += "?";
}
if (array_cnt > 0) {
code += "[";
for (size_t i = 1; i < array_cnt; i++) code += ",";
code += "]";
if (use_span && array_cnt > 0) {
code += "ReadOnlySpan<" + GenTypeBasic(type) + ">";
} else {
code += GenTypeBasic(type);
if (field.IsScalarOptional()) {
code += "?";
}
if (array_cnt > 0) {
code += "[";
for (size_t i = 1; i < array_cnt; i++) code += ",";
code += "]";
}
}
code += " ";
code += nameprefix;
Expand All @@ -574,10 +594,12 @@ class CSharpGenerator : public BaseGenerator {

// Recusively generate struct construction statements of the form:
// builder.putType(name);
// and insert manual padding.
// and insert manual padding. When use_span is true, multi-dimensional array
// accesses use flat indexing (e.g. [i*N+j]) instead of [i,j].
void GenStructBody(const StructDef& struct_def, std::string* code_ptr,
const char* nameprefix, size_t index = 0,
bool in_array = false) const {
bool in_array = false, bool use_span = false,
std::vector<int> array_dims = {}) const {
std::string& code = *code_ptr;
std::string indent((index + 1) * 2, ' ');
code += indent + " builder.Prep(";
Expand All @@ -594,21 +616,23 @@ class CSharpGenerator : public BaseGenerator {
if (IsStruct(field_type)) {
GenStructBody(*field_type.struct_def, code_ptr,
(nameprefix + (field.name + "_")).c_str(), index,
in_array);
in_array, use_span, array_dims);
} else {
const auto& type =
IsArray(field_type) ? field_type.VectorType() : field_type;
const auto index_var = "_idx" + NumToString(index);
auto current_dims = array_dims;
if (IsArray(field_type)) {
code += indent + " for (int " + index_var + " = ";
code += NumToString(field_type.fixed_length);
code += "; " + index_var + " > 0; " + index_var + "--) {\n";
in_array = true;
current_dims.push_back(field_type.fixed_length);
}
if (IsStruct(type)) {
GenStructBody(*field_type.struct_def, code_ptr,
(nameprefix + (field.name + "_")).c_str(), index + 1,
in_array);
in_array, use_span, current_dims);
} else {
code += IsArray(field_type) ? " " : "";
code += indent + " builder.Put";
Expand All @@ -619,9 +643,24 @@ class CSharpGenerator : public BaseGenerator {
size_t array_cnt = index + (IsArray(field_type) ? 1 : 0);
if (array_cnt > 0) {
code += "[";
for (size_t i = 0; in_array && i < array_cnt; i++) {
code += "_idx" + NumToString(i) + "-1";
if (i != (array_cnt - 1)) code += ",";
if (use_span && in_array && array_cnt > 1) {
// Flat indexing for Span<T>: [(_idx0-1)*stride + (_idx1-1)]
for (size_t i = 0; i < array_cnt; i++) {
if (i > 0) code += " + ";
code += "(_idx" + NumToString(i) + "-1)";
int stride = 1;
for (size_t j = i + 1; j < current_dims.size(); j++) {
stride *= current_dims[j];
}
if (stride > 1) {
code += "*" + NumToString(stride);
}
}
} else {
for (size_t i = 0; in_array && i < array_cnt; i++) {
code += "_idx" + NumToString(i) + "-1";
if (i != (array_cnt - 1)) code += ",";
}
}
code += "]";
}
Expand Down Expand Up @@ -1374,7 +1413,22 @@ class CSharpGenerator : public BaseGenerator {
flatbuffers::FieldDef* key_field = nullptr;
if (struct_def.fixed) {
struct_has_create = true;
// create a struct constructor function
const bool has_arrays = StructHasArrayFields(struct_def);
if (has_arrays) {
// Span version: array params become Span<T>, multi-dim uses flat index
code += "#if ENABLE_SPAN_T\n";
code += " public static " + GenOffsetType(struct_def) + " ";
code += "Create";
code += struct_def.name + "(FlatBufferBuilder builder";
GenStructArgs(struct_def, code_ptr, "", 0, true);
code += ") {\n";
GenStructBody(struct_def, code_ptr, "", 0, false, true);
code += " return ";
code += GenOffsetConstruct(struct_def, "builder.Offset");
code += ";\n }\n";
code += "#else\n";
}
// Original version (also the only version when no arrays)
code += " public static " + GenOffsetType(struct_def) + " ";
code += "Create";
code += struct_def.name + "(FlatBufferBuilder builder";
Expand All @@ -1384,6 +1438,9 @@ class CSharpGenerator : public BaseGenerator {
code += " return ";
code += GenOffsetConstruct(struct_def, "builder.Offset");
code += ";\n }\n";
if (has_arrays) {
code += "#endif\n";
}
} else {
// Generate a method that creates a table in one go. This is only possible
// when the table has no struct fields, since those have to be created
Expand Down Expand Up @@ -2365,6 +2422,73 @@ class CSharpGenerator : public BaseGenerator {
code += " }\n";
}

// Generate one pack declaration: either flat (for Span) or multi-dim.
void GenPackDeclArray(std::string& code, const std::string& name,
const Type& field_type,
const std::vector<FieldArrayLength>& array_lengths,
const std::vector<FieldArrayLength>& array_only_lengths,
bool flat) const {
code += " var " + name + " = ";
code += "new " + GenTypeBasic(field_type) + "[";
if (flat) {
int total = 1;
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
total *= array_only_lengths[i].length;
}
code += NumToString(total);
} else {
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
if (i != 0) code += ",";
code += NumToString(array_only_lengths[i].length);
}
}
code += "];\n";
code += " ";
// initialize array
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
auto idx = "idx" + NumToString(i);
code += "for (var " + idx + " = 0; " + idx + " < " +
NumToString(array_only_lengths[i].length) + "; ++" + idx +
") {";
}
if (flat) {
// flat indexing: name[idx0*d1 + idx1]
code += name + "[";
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
if (i > 0) code += " + ";
code += "idx" + NumToString(i);
int stride = 1;
for (size_t j = i + 1; j < array_only_lengths.size(); ++j) {
stride *= array_only_lengths[j].length;
}
if (stride > 1) code += "*" + NumToString(stride);
}
code += "]";
} else {
// multi-dim indexing: name[idx0,idx1]
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
auto idx = "idx" + NumToString(i);
if (i == 0) {
code += name + "[" + idx;
} else {
code += "," + idx;
}
}
code += "]";
}
code += " = _o";
for (size_t i = 0, j = 0; i < array_lengths.size(); ++i) {
code += "." + ConvertCase(array_lengths[i].name, Case::kUpperCamel);
if (array_lengths[i].length <= 0) continue;
code += "[idx" + NumToString(j++) + "]";
}
code += ";";
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
code += "}";
}
code += "\n";
}

void GenStructPackDecl_ObjectAPI(
const StructDef& struct_def, std::string* code_ptr,
std::vector<FieldArrayLength>& array_lengths) const {
Expand Down Expand Up @@ -2394,50 +2518,25 @@ class CSharpGenerator : public BaseGenerator {
for (size_t i = 0; i < array_lengths.size(); ++i) {
name += "_" + array_lengths[i].name;
}
code += " var " + name + " = ";
if (array_only_lengths.size() > 0) {
code += "new " + GenTypeBasic(field_type) + "[";
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
if (i != 0) {
code += ",";
}
code += NumToString(array_only_lengths[i].length);
}
code += "];\n";
code += " ";
// initialize array
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
auto idx = "idx" + NumToString(i);
code += "for (var " + idx + " = 0; " + idx + " < " +
NumToString(array_only_lengths[i].length) + "; ++" + idx +
") {";
}
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
auto idx = "idx" + NumToString(i);
if (i == 0) {
code += name + "[" + idx;
} else {
code += "," + idx;
}
}
code += "] = _o";
for (size_t i = 0, j = 0; i < array_lengths.size(); ++i) {
code += "." + ConvertCase(array_lengths[i].name, Case::kUpperCamel);
if (array_lengths[i].length <= 0) continue;
code += "[idx" + NumToString(j++) + "]";
}
code += ";";
for (size_t i = 0; i < array_only_lengths.size(); ++i) {
code += "}";
}
if (array_only_lengths.size() > 1) {
// Multi-dim case: needs flat arrays for Span, multi-dim for legacy
code += "#if ENABLE_SPAN_T\n";
GenPackDeclArray(code, name, field_type, array_lengths,
array_only_lengths, true);
code += "#else\n";
GenPackDeclArray(code, name, field_type, array_lengths,
array_only_lengths, false);
code += "#endif\n";
} else if (array_only_lengths.size() == 1) {
GenPackDeclArray(code, name, field_type, array_lengths,
array_only_lengths, false);
} else {
code += "_o";
code += " var " + name + " = _o";
for (size_t i = 0; i < array_lengths.size(); ++i) {
code += "." + ConvertCase(array_lengths[i].name, Case::kUpperCamel);
}
code += ";";
code += ";\n";
}
code += "\n";
}
array_lengths.pop_back();
}
Expand Down
24 changes: 16 additions & 8 deletions tests/FlatBuffers.Test/FlatBuffersExampleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,22 +398,32 @@ public void TestFixedLenghtArrays()
float a;
int[] b = new int[15];
sbyte c;
int[,] d_a = new int[2, 2];
TestEnum[] d_b = new TestEnum[2];
TestEnum[,] d_c = new TestEnum[2, 2];
long[,] d_d = new long[2, 2];
int e;
long[] f = new long[2];

a = 0.5f;
for (int i = 0; i < 15; i++) b[i] = i;
c = 1;
d_b[0] = TestEnum.B;
d_b[1] = TestEnum.C;
e = 2;
f[0] = -1;
f[1] = 1;

#if ENABLE_SPAN_T
// Flat arrays for Span<T> parameters
int[] d_a = new int[] { 1, 2, 3, 4 };
TestEnum[] d_c = new TestEnum[] { TestEnum.A, TestEnum.B, TestEnum.C, TestEnum.B };
long[] d_d = new long[] { -1, 1, -2, 2 };
#else
int[,] d_a = new int[2, 2];
TestEnum[,] d_c = new TestEnum[2, 2];
long[,] d_d = new long[2, 2];
d_a[0, 0] = 1;
d_a[0, 1] = 2;
d_a[1, 0] = 3;
d_a[1, 1] = 4;
d_b[0] = TestEnum.B;
d_b[1] = TestEnum.C;
d_c[0, 0] = TestEnum.A;
d_c[0, 1] = TestEnum.B;
d_c[1, 0] = TestEnum.C;
Expand All @@ -422,9 +432,7 @@ public void TestFixedLenghtArrays()
d_d[0, 1] = 1;
d_d[1, 0] = -2;
d_d[1, 1] = 2;
e = 2;
f[0] = -1;
f[1] = 1;
#endif

Offset<ArrayStruct> arrayOffset = ArrayStruct.CreateArrayStruct(
builder, a, b, c, d_a, d_b, d_c, d_d, e, f);
Expand Down
Loading