Skip to content

Commit

Permalink
fix: struct ordering
Browse files Browse the repository at this point in the history
Before objects were either inserted before already inserted objects that
reference them or at the end of the list. This algorithm fails when
structs are reference structs that reference other structs. Now
dependent objects are always inserted first. To implement quickly it
naively iterates over the object list N times to insert all of the
remaining objects.
  • Loading branch information
G4Vi committed Dec 4, 2024
1 parent 34d5259 commit a785d85
Show file tree
Hide file tree
Showing 2 changed files with 4,509 additions and 22 deletions.
92 changes: 70 additions & 22 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@ function cppIdentifer(s: string) {
return helpers.snakeToPascalCase(s);
}

function objectReferencesObject(existing: ObjectType, name: string) {
for (const prop of existing.properties) {
if (prop.kind === 'object') {
const object = prop as ObjectType
if (object.name === name) {
return true
}
}
}
return false
}

function v2ToCppTypeXInner(type: XtpNormalizedType, refnamespace: string): string {
switch (type.kind) {
case 'string':
Expand Down Expand Up @@ -328,29 +316,89 @@ function isTypeUntypedObject(type: XtpNormalizedType) {
return false
}

function addObject(objects: Schema[], schema: Schema) {
// find the object's direct dependencies
const names = new Set();
const object = schema.xtpType as ObjectType
for (const prop of object.properties) {
if (prop.kind === 'object') {
const object = prop as ObjectType
if (object.name) {
names.add(object.name)
}
} else if (prop.kind === 'array') {
const arrayType = prop as ArrayType
if (arrayType.elementType.kind === 'object') {
const object = arrayType.elementType as ObjectType
if (object.name) {
names.add(object.name)
}
}
} else if (prop.kind === 'map') {
const { keyType, valueType } = prop as MapType
if (valueType.kind === 'object') {
const object = valueType as ObjectType
if (object.name) {
names.add(object.name)
}
}
}
}
// loop until after the dependencies or end of objects
let i = 0;
for (; i < objects.length; i++) {
if (names.size === 0) {
break
}
const existing = objects[i]
const existingObject = existing.xtpType as ObjectType;
names.delete(existingObject.name)
}
// not all deps found, can't add yet
if (names.size) {
return false
}
// add
objects.splice(i, 0, schema);
return true
}

export function render() {
const tmpl = Host.inputString();
const prevctx = getContext();

const enums: Schema[] = [];
const todoObjects: Schema[] = [];
const objects: Schema[] = [];
Object.values(prevctx.schema.schemas).forEach(schema => {
for (const schema of Object.values(prevctx.schema.schemas)) {
if (helpers.isEnum(schema)) {
enums.push(schema);
} else if (helpers.isObject(schema)) {
const object = schema.xtpType as ObjectType;
// insertion sort objects ahead of objects that use them
let i = 0;
for (; i < objects.length; i++) {
if (objectReferencesObject(objects[i].xtpType as ObjectType, object.name)) {
break;
}
// insertion sort add object after its dependencies
// if it's dependencies aren't in objects, defer
if (!addObject(objects, schema)) {
todoObjects.push(schema)
}
objects.splice(i, 0, schema); // we need Schema as it has the required attribute
} else {
throw new Error("unhandled schema type " + schema.xtpType.kind)
}
});
}
// add the remaining objects
// rather than doing dependency analysis
// each iteration loop through all the objects until we add an object
const maxIterations = todoObjects.length
for (let iter = 0; iter < maxIterations; iter++) {
for (let i = 0; i < todoObjects.length; i++) {
if (addObject(objects, todoObjects[i])) {
todoObjects.splice(i, 1)
break
}
}
}
if (todoObjects.length) {
throw new Error("Failed to add remaning objects")
}

// sort the properties for efficient struct layout
for (const object of objects) {
object.properties.sort((a: Property, b: Property) => {
Expand Down
Loading

0 comments on commit a785d85

Please sign in to comment.