lava 0.4.9

Rust wrapper to manipulate Vulkan more conveniently than with bindings.
Documentation
const { getRawVkTypeName, getWrappedVkTypeName, getFieldsInformation, addUsesToSet, isStructOrHandle, isOutputHandleStruct, documentType } = require('./utils');
const { getStruct } = require('./parse');
const { genImplFlags, genFlagBitsDoc } = require('./bit_flags');

function generateVkStructDefinition(cDef) {
    const def = {
        typeName: cDef.name,
        extension: cDef.extension,
        rawTypeName: getRawVkTypeName(cDef.name),
        wrappedTypeName: getWrappedVkTypeName(cDef.name),
        fields: getFieldsInformation(cDef.fields, cDef.name),
        cFields: cDef.fields
    };

    const lifetimeTree = assignLifetimes(def.fields);

    def.lifetimes = lifetimeTree.getSpecs();
    def.lifetimesRestrictions = lifetimeTree.getRestrictions();
    def.staticLifetimes = lifetimeTree.getStatics();

    cDef.lifetimes = def.lifetimes; // Necessary for typedefs

    // if (def.typeName === 'VkDisplayProperties') {
    //     def.fields.forEach(field => {
    //         console.log(`--> ${field.varName}`)
    //         console.log(!!field.toRaw)
    //         console.log(!field.wrappedType);
    //         console.log(!!field.defaultValue)
    //     })
    // }

    const isFlags = cDef.name === 'VkPhysicalDeviceFeatures';

    if (isFlags) {
        for (let i = 0; i < def.fields.length; ++i) {
            def.fields[i].value = Math.pow(2, i) + '';
        }
    }

    return [
        genUses(def),
        getWrappedStructDeclaration(def, isFlags),
        genRawStructDeclaration(def),
        genImplVkWrappedType(def),
        genImplVkRawType(def),
        genImplDefault(def),
        genImplVkSetup(def),
        genImplVkFree(def),
        isFlags ? genImplFlags(def, 'u64') : null
    ];
}

function genUses(def) {
    const uses = new Set([
        'std::os::raw::c_char',
        'std::ops::Deref',
        'std::ptr',
        'std::cmp',
        'std::mem',
        `utils::c_bindings::*`,
        'utils::vk_convert::*',
        'utils::vk_null::*',
        'utils::vk_ptr::*',
        'utils::vk_traits::*',
        `vulkan::vk::*`
    ]);

    addUsesToSet(uses, def, def.fields);

    return Array.from(uses).map(str => `use ${str};`);
}

function genRawStructDeclaration(cDef) {
    const derivedTraits = [];
    
    if (!cDef.cFields.some(field => (field.arraySize && +field.arraySize > 32) || field.typeName === 'VkPhysicalDeviceProperties')) {
        derivedTraits.push('Debug', 'Copy', 'Clone');
    }

    return [
        `#[doc(hidden)]`,
        `#[repr(C)]`,
        derivedTraits.length ? `#[derive(${derivedTraits.join(', ')})]` : null,
        `pub struct ${cDef.rawTypeName}`,
            cDef.fields.map(field => `pub ${field.varName}: ${field.rawType},`)
    ];
}

function getWrappedStructDeclaration(def, isFlags) {
    const fields = getWrappedFields(def);
    const derivedTraits = ['Debug', 'Clone'];
    
    return [
        documentType(def, isFlags ? genFlagBitsDoc(def) : null),
        `#[derive(${derivedTraits.join(', ')})]`,
        `pub struct ${def.wrappedTypeName}${def.lifetimes}${def.lifetimesRestrictions}`,
            fields.map(field => `pub ${field.varName}: ${field.wrappedType},`)
    ];
}

function getWrappedFields(def) {
    return def.fields.filter(field => field.wrappedType);
}

const SPECIAL_NON_RAW_TYPES = ['VkClearValue', 'VkClearColorValue'];

function isConvertibleFromRawToWrapped(def) {
    return def.fields.every(field => !SPECIAL_NON_RAW_TYPES.includes(field.typeName) && (!field.wrappedType || field.toWrapped));
}

function isConvertibleFromWrappedToRaw(def) {
    return def.fields.every(field => field.toRaw && (!field.wrappedType || field.defaultValue)) && canHaveStaticValue(def);
}

function genImplVkRawType(def) {
    if (isConvertibleFromRawToWrapped(def)) {
        const wrappedFields = getWrappedFields(def);

        return [
            `impl${def.lifetimes} VkRawType<${def.wrappedTypeName}${def.lifetimes}> for ${def.rawTypeName}`, [
                `fn vk_to_wrapped(src: &${def.rawTypeName}) -> ${def.wrappedTypeName}${def.lifetimes}`, [
                    def.wrappedTypeName,
                    wrappedFields.map(field => {
                        return `${field.varName}: ${field.toWrapped(varName => `src.${varName}`)},`;
                    })
                ],
            ]
        ];
    }
}

function genImplVkWrappedType(def) {
    if (isConvertibleFromWrappedToRaw(def)) {
        const rawFields = def.fields;

        return [
            `impl${def.lifetimes} VkWrappedType<${def.rawTypeName}> for ${def.wrappedTypeName}${def.lifetimes}${def.lifetimesRestrictions}`, [
                `fn vk_to_raw(src: &${def.wrappedTypeName}, dst: &mut ${def.rawTypeName})`,
                rawFields.map(field => `dst.${field.varName} = ${field.toRaw(varName => `src.${varName}`)};`)
            ]
        ];
    }
}

function genImplDefault(def) {
    if (isConvertibleFromWrappedToRaw(def)) {
        const wrappedFields = getWrappedFields(def);

        return [
            `impl Default for ${def.wrappedTypeName}${def.staticLifetimes}`, [
                `fn default() -> ${def.wrappedTypeName}${def.staticLifetimes}`, [
                    def.wrappedTypeName,
                    wrappedFields.map(field => `${field.varName}: ${field.defaultValue},`)
                ]
            ]
        ];
    }
}

function genImplVkSetup(def) {
    return [
        `impl${def.lifetimes} VkSetup for ${def.wrappedTypeName}${def.lifetimes}${def.lifetimesRestrictions}`, [
            `fn vk_setup(&mut self, fn_table: *mut VkFunctionTable)`,
            def.fields.map(field => {
                if (isStructOrHandle(field) && field.wrappedType === field.wrappedTypeName) {
                    return `VkSetup::vk_setup(&mut self.${field.varName}, fn_table);`
                }
            }).filter(x => x)
        ]
    ]
}

function genImplVkFree(def) {
    const fieldsToFree = def.fields.filter(field => field.freeRaw);

    return [
        `impl VkFree for ${def.rawTypeName}`, [
            `fn vk_free(&self)`,
            fieldsToFree.map(field => `${field.freeRaw(varName => `self.${varName}`)};`)
        ]
    ]
}

function canHaveStaticValue(def) {
    if (isOutputHandleStruct(def.typeName)) {
        return false;
    }

    if (def.arraySize && def.typeName === 'char') {
        return false;
    } else {
        const struct = getStruct(def);

        if (struct) {
            return struct.fields.every(field => field.typeName === def.typeName || canHaveStaticValue(field));
        } else {
            return true;
        }
    }
}

class LetterCounter {
    constructor() {
        this._counter = 0;
        this._initialized = false;
    }

    next() {
        return "'" + String.fromCharCode('a'.charCodeAt(0) + this._counter++);
    }
}

class LifetimeTree {
    constructor(letter, parentLetter) {
        this._letter = letter;
        this._parentLetter = parentLetter;
        this._children = [];
    }

    add(counter) {
        const child = new LifetimeTree(counter && counter.next(), this._letter || this._parentLetter);
        this._children.push(child);

        return child;
    }

    _collect(obj = {}) {
        if (this._letter) {
            obj[this._letter] = this._parentLetter;
        }

        this._children.forEach(child => child._collect(obj));

        return obj;
    }

    letter() {
        return this._letter;
    }

    getSpecs() {
        const letters = Object.keys(this._collect());

        if (!letters.length) {
            return '';
        }

        return `<${letters.join(', ')}>`;
    }

    getRestrictions() {
        const restrictions = Object.entries(this._collect()).filter(entry => entry[1]);

        if (!restrictions.length) {
            return '';
        }

        return `\n    where\n${restrictions.map(([key, value]) => `        ${key}: ${value},\n`).join('')}`
    }

    getStatics() {
        const letters = Object.keys(this._collect());

        if (!letters.length) {
            return '';
        }

        return `<${letters.map(() => "'static").join(', ')}>`;
    }
}

function assignLifetimes(fields, counter, root) {
    counter = counter || new LetterCounter();
    root = root || new LifetimeTree();

    for (let field of fields) {
        let tree = root;

        if (field.wrappedType) {
            if (field.wrappedType.includes('&')) {
                tree = tree.add(counter);
                field.wrappedType = field.wrappedType.replace('&', `&${tree.letter()} `);

                if (field.wrappedType.includes(`[&str]`)) {
                    tree = tree.add(counter);
                    field.wrappedType = field.wrappedType.replace('[&str]', `[&${tree.letter()} str]`);
                }

                if (field.wrappedType.includes(`[&${field.wrappedTypeName}]`)) {
                    tree = tree.add(counter);
                    field.wrappedType = field.wrappedType.replace(`[&${field.wrappedTypeName}]`, `[&${tree.letter()} ${field.wrappedTypeName}]`);
                }
            }

            if (field.wrappedTypeName.startsWith('Vk')) {
                const structDef = getStruct(field);

                if (structDef) {
                    const structFields = getFieldsInformation(structDef.fields, structDef.name);
                    tree = assignLifetimes(structFields, counter, tree.add(null));
                    field.wrappedType = field.wrappedType.replace(field.wrappedTypeName, field.wrappedTypeName + tree.getSpecs());
                }
            }
        }
    }

    return root;
}

module.exports = {
    generateVkStructDefinition
};