From 3a9645a55f649acb5045846263e5cdc5adbd95fa Mon Sep 17 00:00:00 2001 From: Florian Rival Date: Thu, 6 Nov 2025 17:19:37 +0100 Subject: [PATCH] Improve documentation with properties of objects and behaviors --- GDevelop.js/Bindings/Bindings.idl | 14 +- GDevelop.js/types.d.ts | 12 +- GDevelop.js/types/gdjsplatform.js | 9 - GDevelop.js/types/gdplatform.js | 1 + .../scripts/extract-extensions-document.js | 31 +- .../app/scripts/extract-reference-document.js | 8 +- .../lib/ExtensionReferenceGenerator.js | 270 +++++++++++++++++- newIDE/app/scripts/lib/IsNullPtr.js | 19 ++ 8 files changed, 315 insertions(+), 49 deletions(-) create mode 100644 newIDE/app/scripts/lib/IsNullPtr.js diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 20daa60d82..fe3b7deb06 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -203,6 +203,7 @@ interface Platform { boolean IsExtensionLoaded([Const] DOMString name); void RemoveExtension([Const] DOMString name); void ReloadBuiltinExtensions(); + [Value] UniquePtrObjectConfiguration CreateObjectConfiguration([Const] DOMString type); [Const, Ref] VectorPlatformExtension GetAllPlatformExtensions(); }; @@ -210,19 +211,8 @@ interface Platform { interface JsPlatform { [Ref] JsPlatform STATIC_Get(); void AddNewExtension([Const, Ref] PlatformExtension extension); - - // Inherited from Platform: - [Const, Value] DOMString GetName(); - [Const, Value] DOMString GetFullName(); - [Const, Value] DOMString GetSubtitle(); - [Const, Value] DOMString GetDescription(); - [Const, Ref] InstructionOrExpressionGroupMetadata GetInstructionOrExpressionGroupMetadata([Const] DOMString name); - boolean IsExtensionLoaded([Const] DOMString name); - void RemoveExtension([Const] DOMString name); - void ReloadBuiltinExtensions(); - - [Const, Ref] VectorPlatformExtension GetAllPlatformExtensions(); }; +JsPlatform implements Platform; interface PairStringVariable { void PairStringVariable(); diff --git a/GDevelop.js/types.d.ts b/GDevelop.js/types.d.ts index b4ca200001..643175e579 100644 --- a/GDevelop.js/types.d.ts +++ b/GDevelop.js/types.d.ts @@ -268,21 +268,13 @@ export class Platform extends EmscriptenObject { isExtensionLoaded(name: string): boolean; removeExtension(name: string): void; reloadBuiltinExtensions(): void; + createObjectConfiguration(type: string): UniquePtrObjectConfiguration; getAllPlatformExtensions(): VectorPlatformExtension; } -export class JsPlatform extends EmscriptenObject { +export class JsPlatform extends Platform { static get(): JsPlatform; addNewExtension(extension: PlatformExtension): void; - getName(): string; - getFullName(): string; - getSubtitle(): string; - getDescription(): string; - getInstructionOrExpressionGroupMetadata(name: string): InstructionOrExpressionGroupMetadata; - isExtensionLoaded(name: string): boolean; - removeExtension(name: string): void; - reloadBuiltinExtensions(): void; - getAllPlatformExtensions(): VectorPlatformExtension; } export class PairStringVariable extends EmscriptenObject { diff --git a/GDevelop.js/types/gdjsplatform.js b/GDevelop.js/types/gdjsplatform.js index 7acd498f57..1924f86d5e 100644 --- a/GDevelop.js/types/gdjsplatform.js +++ b/GDevelop.js/types/gdjsplatform.js @@ -2,15 +2,6 @@ declare class gdJsPlatform extends gdPlatform { static get(): gdJsPlatform; addNewExtension(extension: gdPlatformExtension): void; - getName(): string; - getFullName(): string; - getSubtitle(): string; - getDescription(): string; - getInstructionOrExpressionGroupMetadata(name: string): gdInstructionOrExpressionGroupMetadata; - isExtensionLoaded(name: string): boolean; - removeExtension(name: string): void; - reloadBuiltinExtensions(): void; - getAllPlatformExtensions(): gdVectorPlatformExtension; delete(): void; ptr: number; }; \ No newline at end of file diff --git a/GDevelop.js/types/gdplatform.js b/GDevelop.js/types/gdplatform.js index 69a43f9eb3..990a42da6b 100644 --- a/GDevelop.js/types/gdplatform.js +++ b/GDevelop.js/types/gdplatform.js @@ -8,6 +8,7 @@ declare class gdPlatform { isExtensionLoaded(name: string): boolean; removeExtension(name: string): void; reloadBuiltinExtensions(): void; + createObjectConfiguration(type: string): gdUniquePtrObjectConfiguration; getAllPlatformExtensions(): gdVectorPlatformExtension; delete(): void; ptr: number; diff --git a/newIDE/app/scripts/extract-extensions-document.js b/newIDE/app/scripts/extract-extensions-document.js index 624975cf83..564e738a1e 100644 --- a/newIDE/app/scripts/extract-extensions-document.js +++ b/newIDE/app/scripts/extract-extensions-document.js @@ -28,6 +28,9 @@ const { groupBy, sortKeys } = require('./lib/ArrayHelpers'); const { generateAllExtensionsSections } = require('./lib/WikiExtensionTable'); /** @typedef {import("./lib/ExtensionReferenceGenerator.js").RawText} RawText */ +/** @typedef {import('../../../GDevelop.js/types').EventsFunctionsExtension} EventsFunctionsExtension */ +/** @typedef {import('../../../GDevelop.js/types').Project} Project */ +/** @typedef {import('../../../GDevelop.js/types').PlatformExtension} PlatformExtension */ /** @typedef {{ tier: 'community' | 'experimental' | 'reviewed', shortDescription: string, authorIds: Array, authors?: Array<{id: string, username: string}>, extensionNamespace: string, fullName: string, name: string, version: string, gdevelopVersion?: string, url: string, headerUrl: string, tags: Array, category: string, previewIconUrl: string, eventsBasedBehaviorsCount: number, eventsFunctionsCount: number}} ExtensionShortHeader */ @@ -60,9 +63,9 @@ const generateAuthorNamesWithLinks = authors => { * (useful as containing author public profiles information). * * @param {any} gd - * @param {any} project (gdProject) + * @param {Project} project * - * @returns {Promise} A promise to all extensions (gdEventsFunctionsExtension) + * @returns {Promise>} */ const addAllExtensionsToProject = async (gd, project) => { const response = await axios.get(extensionShortHeadersUrl); @@ -149,23 +152,27 @@ const getAllExtensionShortHeaders = async () => { * Create a page for an extension. * @param {any} gd * @param {any} project (gdProject) - * @param {any} extension The extension (gdEventsFunctionsExtension) + * @param {EventsFunctionsExtension} eventsFunctionsExtension * @param {ExtensionShortHeader} extensionShortHeader * @param {boolean} isExperimental The tier */ const createExtensionReferencePage = async ( gd, project, - extension, + eventsFunctionsExtension, extensionShortHeader, isExperimental ) => { - const extensionMetadata = generateEventsFunctionExtensionMetadata( + const platformExtension = generateEventsFunctionExtensionMetadata( gd, project, - extension + eventsFunctionsExtension ); - const extensionReference = generateExtensionReference(extensionMetadata); + const extensionReference = generateExtensionReference({ + platform: gd.JsPlatform.get(), + extension: platformExtension, + eventsFunctionsExtension, + }); const referencePageContent = rawTextsToString( generateExtensionRawText( extensionReference, @@ -179,7 +186,7 @@ const createExtensionReferencePage = async ( ) ); - const folderName = getExtensionFolderName(extension.getName()); + const folderName = getExtensionFolderName(platformExtension.getName()); const extensionReferenceFilePath = path.join( extensionsRootPath, folderName, @@ -246,7 +253,7 @@ const generateExtensionFooterText = ({ extension }) => { ` --- -*This page is an auto-generated reference page about the **${extension.getFullName()}** extension, made by the community of [GDevelop, the open-source, cross-platform game engine designed for everyone](https://gdevelop.io/).*` + +*This page is an auto-generated reference page about the **${extension.getFullName()}** extension for [GDevelop, the open-source, AI-powered, cross-platform game engine designed for everyone](https://gdevelop.io/).*` + ' ' + 'Learn more about [all GDevelop community-made extensions here](/gdevelop5/extensions).', }; @@ -254,9 +261,9 @@ const generateExtensionFooterText = ({ extension }) => { /** * Generate the metadata for the events based extension - * @param {any} project A project containing of the extensions (gdProject) - * @param {any} eventsFunctionsExtension An extension (gdEventsFunctionsExtension) - * @returns {any} the extension metadata (gdPlatformExtension) + * @param {Project} project + * @param {EventsFunctionsExtension} eventsFunctionsExtension + * @returns {PlatformExtension} */ const generateEventsFunctionExtensionMetadata = ( gd, diff --git a/newIDE/app/scripts/extract-reference-document.js b/newIDE/app/scripts/extract-reference-document.js index f63d194d63..26ae1f7b1d 100644 --- a/newIDE/app/scripts/extract-reference-document.js +++ b/newIDE/app/scripts/extract-reference-document.js @@ -82,7 +82,11 @@ const generateAllExtensionReferences = gd => { /** @type {Array} */ const extensionReferences = mapVector( platformExtensions, - generateExtensionReference + (platformExtension) => generateExtensionReference({ + platform: gd.JsPlatform.get(), + extension: platformExtension, + eventsFunctionsExtension: null, + }) ); return extensionReferences; @@ -157,7 +161,7 @@ const generateExtensionFooterText = ({ extension }) => { text: `\n\n---\n\n` + generateBuiltInExtensionNote({ extension }) + - `*This page is an auto-generated reference page about the **${extension.getFullName()}** feature of [GDevelop, the open-source, cross-platform game engine designed for everyone](https://gdevelop.io/).*` + + `*This page is an auto-generated reference page about the **${extension.getFullName()}** feature of [GDevelop, the open-source, AI-powered, cross-platform game engine designed for everyone](https://gdevelop.io/).*` + ' ' + 'Learn more about [all GDevelop features here](/gdevelop5/all-features).', }; diff --git a/newIDE/app/scripts/lib/ExtensionReferenceGenerator.js b/newIDE/app/scripts/lib/ExtensionReferenceGenerator.js index aa7c7c9f72..53939612e1 100644 --- a/newIDE/app/scripts/lib/ExtensionReferenceGenerator.js +++ b/newIDE/app/scripts/lib/ExtensionReferenceGenerator.js @@ -1,15 +1,23 @@ // @ts-check const { mapFor } = require('./MapFor'); const { generateReadMoreLink } = require('./WikiHelpLink'); +const { isNullPtr } = require('./IsNullPtr'); +const { mapVector } = require('./MapFor'); // Types definitions used in this script: +/** @typedef {import('../../../../GDevelop.js/types').Platform} Platform */ /** @typedef {import('../../../../GDevelop.js/types').PlatformExtension} PlatformExtension */ +/** @typedef {import('../../../../GDevelop.js/types').EventsFunctionsExtension} EventsFunctionsExtension */ /** @typedef {import('../../../../GDevelop.js/types').MapStringExpressionMetadata} MapStringExpressionMetadata */ /** @typedef {import('../../../../GDevelop.js/types').ExpressionMetadata} ExpressionMetadata */ /** @typedef {import('../../../../GDevelop.js/types').ObjectMetadata} ObjectMetadata */ /** @typedef {import('../../../../GDevelop.js/types').BehaviorMetadata} BehaviorMetadata */ /** @typedef {import('../../../../GDevelop.js/types').ParameterMetadata} ParameterMetadata */ +/** @typedef {import('../../../../GDevelop.js/types').MapStringPropertyDescriptor} MapStringPropertyDescriptor */ +/** @typedef {import('../../../../GDevelop.js/types').NamedPropertyDescriptor} NamedPropertyDescriptor */ +/** @typedef {import('../../../../GDevelop.js/types').PropertiesContainer} PropertiesContainer */ +/** @typedef {import('../../../../GDevelop.js/types').PropertyDescriptor} PropertyDescriptor */ /** * @typedef {Object} RawText A text to be shown on a page @@ -25,6 +33,7 @@ const { generateReadMoreLink } = require('./WikiHelpLink'); /** * @typedef {Object} ObjectReference * @prop {ObjectMetadata} objectMetadata The object. + * @prop {Array} propertiesReferenceTexts Reference texts for the object properties. * @prop {Array} actionsReferenceTexts Reference texts for the object actions. * @prop {Array} conditionsReferenceTexts Reference texts for the object conditions. * @prop {Array} expressionsReferenceTexts Reference texts for the object expressions. @@ -33,6 +42,8 @@ const { generateReadMoreLink } = require('./WikiHelpLink'); /** * @typedef {Object} BehaviorReference * @prop {BehaviorMetadata} behaviorMetadata The behavior. + * @prop {Array} propertiesReferenceTexts Reference texts for the behavior properties. + * @prop {Array} sharedPropertiesReferenceTexts Reference texts for the behavior shared properties. * @prop {Array} actionsReferenceTexts Reference texts for the behavior actions. * @prop {Array} conditionsReferenceTexts Reference texts for the behavior conditions. * @prop {Array} expressionsReferenceTexts Reference texts for the behavior expressions. @@ -41,6 +52,7 @@ const { generateReadMoreLink } = require('./WikiHelpLink'); /** * @typedef {Object} ExtensionReference * @prop {PlatformExtension} extension The extension. + * @prop {EventsFunctionsExtension | null} eventsFunctionsExtension The "source" events-based extension. * @prop {Array} freeExpressionsReferenceTexts Reference texts for free expressions. * @prop {Array} freeActionsReferenceTexts Reference texts for free actions. * @prop {Array} freeConditionsReferenceTexts Reference texts for free conditions. @@ -166,7 +178,9 @@ const sanitizeExpressionDescription = str => { ); }; -const translateTypeToHumanReadableDescription = type => { +const translateTypeToHumanReadableDescription = anyType => { + const type = anyType.toLowerCase(); + if (type === 'number') return '🔢 Number'; if (type === 'expression') return '🔢 Number'; if (type === 'camera') return '🔢 Camera index (Number)'; @@ -208,6 +222,11 @@ const translateTypeToHumanReadableDescription = type => { if (type === 'yesorno') return '❓ Yes or No'; if (type === 'trueorfalse') return '❓ True or False'; + if (type === 'multilinestring') return '🔤 Multiline text (String)'; + if (type === 'boolean') return '🔘 Boolean'; + if (type === 'color') return '🎨 Color'; + if (type === 'resource') return '🗂️ Resource'; + return type; }; @@ -575,10 +594,188 @@ const sortReferenceTexts = (referenceText1, referenceText2) => { }; /** - * @param {PlatformExtension} extension + * @param {string} text + * @returns {string} + */ +const sanitizePropertyDescription = text => { + const sanitizedText = text.replace(/\n/g, ' ').trim(); + if (sanitizedText.endsWith('.')) return sanitizedText; + return sanitizedText + '.'; +}; + +/** + * @param {{ propertiesMetadata?: MapStringPropertyDescriptor, propertiesContainer?: PropertiesContainer }} options + * @returns {Array} + */ +const generatePropertiesReferenceTexts = ({ + propertiesMetadata, + propertiesContainer, +}) => { + /** + * @param {string} name + * @param {PropertyDescriptor | NamedPropertyDescriptor} property + * @returns {ReferenceText | null} + */ + const generatePropertyReferenceText = (name, property) => { + if (name.startsWith('_')) return null; + if (property.isHidden()) return null; + + if (property.getType() === 'Behavior') { + return null; + } + + const type = property.getType(); + const measurementUnit = property.getMeasurementUnit(); + const measurementUnitText = measurementUnit.isUndefined() + ? null + : measurementUnit.getLabel(); + + const choices = + type.toLowerCase() === 'choice' + ? [ + ...mapVector(property.getChoices(), choice => choice.getValue()), + // TODO Remove this once we made sure no built-in extension still use `addExtraInfo` instead of `addChoice`. + ...property.getExtraInfo().toJSArray(), + ] + : null; + const information = [ + translateTypeToHumanReadableDescription(type), + choices + ? `one of: ${choices.map(choice => `"${choice}"`).join(', ')}` + : null, + measurementUnitText, + ].filter(Boolean); + + return { + orderKey: name, + text: [ + `- **${property.getLabel() || name}** (${information.join(', ')}).`, + property.getDescription() + ? `${sanitizePropertyDescription(property.getDescription())}` + : null, + property.getValue() + ? `Default value is \`${property.getValue()}\`.` + : null, + ] + .filter(Boolean) + .join(' '), + }; + }; + + /** + * @param {string} name + * @param {PropertyDescriptor | NamedPropertyDescriptor} property + * @returns {ReferenceText | null} + */ + const generatePropertyInternalReferenceText = (name, property) => { + if (name.startsWith('_')) return null; + if (property.isHidden()) return null; + + const padding = ' '; + + if (property.getType() === 'Behavior') { + return { + orderKey: name, + text: `${padding}> This behavior must be used on an object also having a behavior with type "${property + .getExtraInfo() + .toJSArray() + .join(', ')}". This is stored on property \`${name}\`.\n`, + }; + } + + const type = property.getType(); + const measurementUnit = property.getMeasurementUnit(); + const measurementUnitText = measurementUnit.isUndefined() + ? null + : measurementUnit.getName(); + + return { + orderKey: name, + text: [ + `${padding}- **${property.getLabel() || + name}** is stored as \`${name}\` (${type}).`, + measurementUnitText ? `Unit is ${measurementUnitText}.` : null, + `Default value is \`${property.getValue()}\`.`, + ] + .filter(Boolean) + .join(' '), + }; + }; + + const propertiesReferenceTexts = [ + ...(propertiesMetadata + ? propertiesMetadata + .keys() + .toJSArray() + .map(propertyName => { + const propertyDescriptor = propertiesMetadata.get(propertyName); + return generatePropertyReferenceText( + propertyName, + propertyDescriptor + ); + }) + : []), + ...(propertiesContainer + ? mapVector(propertiesContainer, namedPropertyDescriptor => { + const propertyName = namedPropertyDescriptor.getName(); + + return generatePropertyReferenceText( + propertyName, + namedPropertyDescriptor + ); + }) + : []), + ].filter(Boolean); + + if (!propertiesReferenceTexts.length) return []; + + const internalPropertiesReferenceTexts = [ + ...(propertiesMetadata + ? propertiesMetadata + .keys() + .toJSArray() + .map(propertyName => { + const propertyDescriptor = propertiesMetadata.get(propertyName); + return generatePropertyInternalReferenceText( + propertyName, + propertyDescriptor + ); + }) + : []), + ...(propertiesContainer + ? mapVector(propertiesContainer, namedPropertyDescriptor => { + const propertyName = namedPropertyDescriptor.getName(); + return generatePropertyInternalReferenceText( + propertyName, + namedPropertyDescriptor + ); + }) + : []), + ].filter(Boolean); + + return [ + ...propertiesReferenceTexts, + { + orderKey: '', + text: '\n??? quote "See internal technical details"\n\n', + }, + ...internalPropertiesReferenceTexts, + ]; +}; + +/** + * @param {{ + * platform: Platform, + * extension: PlatformExtension, + * eventsFunctionsExtension: EventsFunctionsExtension | null + * }} options * @returns {ExtensionReference} */ -const generateExtensionReference = extension => { +const generateExtensionReference = ({ + platform, + extension, + eventsFunctionsExtension, +}) => { const extensionExpressions = extension.getAllExpressions(); const extensionStrExpressions = extension.getAllStrExpressions(); @@ -595,6 +792,45 @@ const generateExtensionReference = extension => { if (objectMetadata.isPrivate()) { return null; } + + const propertiesReferenceTexts = []; + + if (eventsFunctionsExtension) { + // Objects from "events-based" extensions: + const eventsBasedObjects = eventsFunctionsExtension.getEventsBasedObjects(); + const objectName = + objectType.split('::').pop() || 'Unrecognized object type format'; + + if (eventsBasedObjects.has(objectName)) { + const eventsBasedObject = eventsBasedObjects.get(objectName); + propertiesReferenceTexts.push( + ...generatePropertiesReferenceTexts({ + propertiesContainer: eventsBasedObject.getPropertyDescriptors(), + }) + ); + } else { + throw new Error( + `Object "${objectType}" not found in events-based extension "${eventsFunctionsExtension.getFullName()}".` + ); + } + } else { + // "Built-in" objects: + const objectConfiguration = platform + .createObjectConfiguration(objectType) + .release(); + if (isNullPtr(objectConfiguration)) { + throw new Error( + `Failed to create object configuration for object type "${objectType}".` + ); + } + + propertiesReferenceTexts.push( + ...generatePropertiesReferenceTexts({ + propertiesMetadata: objectConfiguration.getProperties(), + }) + ); + } + const actionsReferenceTexts = generateInstructionsReferenceRowsTexts({ areConditions: false, instructionsMetadata: extension.getAllActionsForObject(objectType), @@ -621,6 +857,7 @@ const generateExtensionReference = extension => { return { objectMetadata, + propertiesReferenceTexts, actionsReferenceTexts, conditionsReferenceTexts, expressionsReferenceTexts, @@ -638,6 +875,13 @@ const generateExtensionReference = extension => { return null; } + const propertiesReferenceTexts = generatePropertiesReferenceTexts({ + propertiesMetadata: behaviorMetadata.getProperties(), + }); + const sharedPropertiesReferenceTexts = generatePropertiesReferenceTexts({ + propertiesMetadata: behaviorMetadata.getSharedProperties(), + }); + const actionsReferenceTexts = generateInstructionsReferenceRowsTexts({ areConditions: false, instructionsMetadata: extension.getAllActionsForBehavior(behaviorType), @@ -668,6 +912,8 @@ const generateExtensionReference = extension => { return { behaviorMetadata, + propertiesReferenceTexts, + sharedPropertiesReferenceTexts, actionsReferenceTexts, conditionsReferenceTexts, expressionsReferenceTexts, @@ -696,6 +942,7 @@ const generateExtensionReference = extension => { return { extension, + eventsFunctionsExtension, freeActionsReferenceTexts, freeConditionsReferenceTexts, freeExpressionsReferenceTexts, @@ -727,7 +974,7 @@ const generateExtensionRawText = ( const withHeaderIfNotEmpty = (texts, { headerName, depth }) => { if (!texts.length) return []; - return [generateHeader({ headerName, depth }), ...texts]; + return [generateHeader({ headerName, depth }), ...texts, { text: '' }]; }; return [ @@ -750,6 +997,7 @@ const generateExtensionRawText = ( ...objectReferences.flatMap(objectReference => { const { objectMetadata, + propertiesReferenceTexts, actionsReferenceTexts, conditionsReferenceTexts, expressionsReferenceTexts, @@ -761,6 +1009,10 @@ const generateExtensionRawText = ( showExtensionName: false, showHelpLink: false, }), + ...withHeaderIfNotEmpty(propertiesReferenceTexts, { + headerName: 'Object properties', + depth: 3, + }), ...withHeaderIfNotEmpty(actionsReferenceTexts, { headerName: 'Object actions', depth: 3, @@ -781,6 +1033,8 @@ const generateExtensionRawText = ( ...behaviorReferences.flatMap(behaviorReference => { const { behaviorMetadata, + propertiesReferenceTexts, + sharedPropertiesReferenceTexts, actionsReferenceTexts, conditionsReferenceTexts, expressionsReferenceTexts, @@ -792,6 +1046,14 @@ const generateExtensionRawText = ( showExtensionName: false, showHelpLink: false, }), + ...withHeaderIfNotEmpty(propertiesReferenceTexts, { + headerName: 'Behavior properties', + depth: 3, + }), + ...withHeaderIfNotEmpty(sharedPropertiesReferenceTexts, { + headerName: 'Behavior shared properties', + depth: 3, + }), ...withHeaderIfNotEmpty(actionsReferenceTexts, { headerName: 'Behavior actions', depth: 3, diff --git a/newIDE/app/scripts/lib/IsNullPtr.js b/newIDE/app/scripts/lib/IsNullPtr.js new file mode 100644 index 0000000000..fbe5ee1ade --- /dev/null +++ b/newIDE/app/scripts/lib/IsNullPtr.js @@ -0,0 +1,19 @@ +// @ts-check + +/** @typedef {import('../../../../GDevelop.js/types').EmscriptenObject} EmscriptenObject */ + +/** + * Check if the Emscripten object is actually a null pointer. + * + * You should normally not use this - instead prefer the null/empty object pattern, + * with a function `IsBadXxx` to check if the object you're manipulating represents + * an null/empty object. + * + * @param {EmscriptenObject} object + * @returns {boolean} + */ +const isNullPtr = object => object.ptr === 0; + +module.exports = { + isNullPtr, +};