lava 0.4.9

Rust wrapper to manipulate Vulkan more conveniently than with bindings.
Documentation
#!/usr/bin/env node

const path = require('path');
const fs = require('fs');
const rimraf = require('rimraf');
const mkdirp = require('mkdirp');

const { getAllEnums, getAllBitFlags, getAllStructs, getAllHandles, getAllFunctions, getAllExtensionNames, getAllTypedefs } = require('./parse');
const { blockToString, toSnakeCase, getWrappedVkTypeName, getRawVkTypeName, documentType } = require('./utils');
const { generateVkStructDefinition } = require('./structs');
const { generateVkEnumDefinition } = require('./enums');
const { generateVkBitFlagsDefinition } = require('./bit_flags');
const { generateVkHandleDefinition } = require('./handles');
const { generateFunctionTableDefinition } = require('./function_table');
const { generateRootFunctionsDefinitions } = require('./root_functions');

const ROOT_DIR_PATH         = path.join(__dirname, '..');
const OUTPUT_DIR_PATH       = path.join(ROOT_DIR_PATH, 'src', 'vulkan');
const STATIC_FILES_DIR_PATH = path.join(__dirname, 'static');

const GENERATED_HEADER      = '// Generated by `scripts/generate.js`\n\n';
const COPIED_HEADER         = '// Copied from `scripts/static/`\n\n';

const STATIC_VK_FUNCTIONS = [
    'vkEnumerateInstanceVersion',
    'vkEnumerateInstanceExtensionProperties',
    'vkEnumerateInstanceLayerProperties',
    'vkCreateInstance'
];

let STRUCTS = null;

function removeFiles() {
    rimraf.sync(OUTPUT_DIR_PATH);
}

function generateFiles() {
    const vkTypes = [
        generateRootFunctions(),
        generateFunctionTable(),
        generateExtensionNames(),
        ...generateEnums(),
        ...generateBitFlags(),
        ...generateStructs(),
        ...generateHandles(),
        ...generateTypedefs()
    ].filter(x => x);

    writeVkTypes(vkTypes);
    copyStaticFiles(STATIC_FILES_DIR_PATH, OUTPUT_DIR_PATH);
    writeModFile(OUTPUT_DIR_PATH);
}

function copyStaticFiles(srcDirPath, dstDirPath) {
    const fileNames = fs.readdirSync(srcDirPath);

    mkdir(dstDirPath);

    fileNames.forEach(fileName => {
        const sourcePath = path.join(srcDirPath, fileName);
        const targetPath = path.join(dstDirPath, fileName);
        const stats = fs.statSync(sourcePath);

        if (stats.isFile()) {
            const fileContent = fs.readFileSync(sourcePath, 'utf8');

            fs.writeFileSync(targetPath, COPIED_HEADER + fileContent, 'utf8');
        } else if (stats.isDirectory()) {
            copyStaticFiles(sourcePath, targetPath);
        }
    
    });
}

function writeVkTypes(types) {
    for (let type of types) {
        let { name, extension, definition } = type;
        extension = extension || 'vk';

        const dirPath = extension ? path.join(OUTPUT_DIR_PATH, extension) : OUTPUT_DIR_PATH;
        const fileName = toSnakeCase(name) + '.rs';
        const filePath = path.join(dirPath, fileName);
        const fileContent = GENERATED_HEADER + definition.filter(x => x).map(blockToString).join('\n\n');

        mkdir(dirPath);
        // console.log(fileContent);
        fs.writeFileSync(filePath, fileContent, 'utf8');
    }
}

function mkdir(dirPath) {
    mkdirp.sync(dirPath);
}

function writeModFile(dirPath) {
    const filePath = path.join(dirPath, 'mod.rs');
    const files = [];
    const directories = [];

    fs.readdirSync(dirPath).forEach(name => {
        if (name !== 'mod.rs') {
            const stats = fs.statSync(path.join(dirPath, name));

            if (stats.isFile()) {
                files.push(name);
            } else if (stats.isDirectory()) {
                directories.push(name);
            }
        }
    });

    const moduleNames = files.map(name => name.replace('.rs', ''));
    const content = [
        directories.map(name => `pub mod ${name};`),
        moduleNames.map(name => `mod ${name};`),
        moduleNames.filter(x => x !== 'vk').map(name => `pub use self::${name}::*;`),
    ].map(list => list.join('\n')).filter(x => x).join('\n\n');

    fs.writeFileSync(filePath, GENERATED_HEADER + content);

    directories.forEach(dirName => writeModFile(path.join(dirPath, dirName)));
}

function generateExtensionNames() {
    return {
        name: 'ExtensionNames',
        extension: 'constants',
        definition: getAllExtensionNames().map(({name, value}) => {
            const doc = `/// \`${value}\``;
            return `${doc}\npub const ${name.toUpperCase()} : &str = ${value};`
        })
    };
}

function generateTypedefs() {
    return getAllTypedefs().map(({baseType, newType}) => {
        if (baseType.name.endsWith('FlagBits')) {
            return null;
        }

        const baseTypeDef = STRUCTS.find(struct => struct.name === baseType.name && struct.extension === baseType.extension);
        const lifetimes = baseTypeDef ? baseTypeDef.lifetimes : '';
        const baseTypeExt = baseType.extension || 'vk';

        // const wrappedTypeName = getWrappedVkTypeName(newType.name);

        return {
            name: newType.name,
            extension: newType.extension,
            definition: [
                `pub type ${getWrappedVkTypeName(newType.name)}${lifetimes} = super::super::${baseTypeExt}::${getWrappedVkTypeName(baseType.name)}${lifetimes};`,
                `#[doc(hidden)]\npub type ${getRawVkTypeName(newType.name)} = super::super::${baseTypeExt}::${getRawVkTypeName(baseType.name)};`
            ]
        };
    });
}

function generateVkTypes(cTypes, generateFunction) {
    return cTypes.map(cDef => {
        const rustDefinition = generateFunction(cDef);

        return {
            name: cDef.name,
            extension: cDef.extension,
            definition: rustDefinition
        };
    });
}

function generateRootFunctions() {
    return {
        name: 'RootFunctions',
        extension: '',
        definition: generateRootFunctionsDefinitions(getAllFunctions().filter(func => STATIC_VK_FUNCTIONS.includes(func.name)))
    };
}

function generateFunctionTable() {
    return {
        name: 'VkFunctionTable',
        extension: '',
        definition: generateFunctionTableDefinition(getAllFunctions().filter(func => !STATIC_VK_FUNCTIONS.includes(func.name)))
    };
}

function generateEnums() {
    return generateVkTypes(getAllEnums(), generateVkEnumDefinition);
}

function generateBitFlags() {
    return generateVkTypes(getAllBitFlags(), generateVkBitFlagsDefinition);
}

function generateStructs() {
    const structs = getAllStructs()
        .filter(struct => struct.fields.every(field => !field.fullType.includes('PFN')));

    STRUCTS = structs;

    return generateVkTypes(structs, generateVkStructDefinition);
}

function isDestroyFunction(func) {
    return func.name.includes('Destroy') || func.name === "vkFreeMemory";
}

function generateHandles() {
    const handles = getAllHandles();
    const functions = getAllFunctions().filter(func => !STATIC_VK_FUNCTIONS.includes(func.name));

    for (let handle of handles) {
        handle.functions = [];
    }

    const destroyFunctions = functions.filter(isDestroyFunction);

    for (let destroyFunction of destroyFunctions) {
        const parentArg = destroyFunction.args.first();
        const destroyedArg = destroyFunction.args.beforeLast();

        if (parentArg !== destroyedArg) {
            const parent = { name: parentArg.typeName, extension: parentArg.extension };

            handles.find(handle => handle.name === destroyedArg.typeName).parent = parent;
        }
    }

    const vkInstance = handles.find(h => h.name === 'VkInstance');

    for (let func of functions) {
        const firstArgType = func.args[0].typeName;
        const secondArg = func.args[1];
        const secondArgType = secondArg && secondArg.typeName;

        let handle = handles.find(handle => {
            return handle.parent &&
                firstArgType === handle.parent.name &&
                secondArgType === handle.name &&
                (!secondArg.isOptional || destroyFunctions.includes(func));
        });

        if (!handle) {
            handle = handles.find(handle => firstArgType === handle.name);
        }

        if (!handle) {
            handle = vkInstance;
        }

        handle.functions.push(func);
    }

    return generateVkTypes(handles, generateVkHandleDefinition);
}

module.exports = {
    generateFiles,
    removeFiles
};