跳到主要内容

Best Practices

This page describes best practices for an OBGX implementation for maximizing compatibility, performance, maintainability, and extensibility. It is not normative and does not define any requirements for an implementation.

Implementing Types

Fundamental Types

Most programming languages have built-in support for most of the fundamental types defined by OBGX. An implementation should use its implementation language's built-in types whenever possible.

Identifier

It is recommended to use the same underlying representation as string for id and refid, since being serializable to human readable text is a common diagnostic requirement for identifiers.

It is recommended to use a separator character that is not allowed in an id to separate the namespace and the identifier in a refid, to achieve contiguous storage and a human readable combination. CherryGrove uses : as the separator character just as Minecraft.

If the chosen type and schema for id and refid doesn't meet the requirement that packs can construct a refid from a game object identifier id value and a namespace id value via only language features, it is recommended to implement the ability to construct a refid from one or two ids in the identifier module.

Void

For languages that have a built-in void type, it is recommended to use it for OBGX functions that return void.

For languages that do not have a built-in void type, it is recommended to use the language's built-in null type (undefined for JavaScript and nil for Lua), if available, or a custom type that represents the absence of a value.

It's worth noting that some languages' built-in null types may be able to convert to or from other OBGX types, which is not allowed by OBGX. In such cases, it is recommended to use a custom type instead, or explicitly warn pack developers about the potential issues.

Enums

Enums in OBGX are similar to tagged unions. For C and C++ implementations, it is recommended to use one enum and one union for representing OBGX enums, since they deliver more performance than std::variant or std::any. For other languages, it is recommended to use the language's built-in tagged union type, if available.

Enum's specification has no unified member/payload representation, and with the limited tools of reflection, this cannot be polyfilled in a generic way. That's why we don't have a module/element for enums to determine its active member, or obtain the payload, like arrays or vectors. You need to implement it in your own way.

Arrays and Vectors

If one element type is very frequently used in arrays and/or vectors and is fixed-size (e.g. integers, floats), it is recommended to use an arena for contiguous storage of the elements. This will improve performance and reduce memory fragmentation.

If the language doesn't provide an operation that obtains the length of an array or vector from its underlying representation, it is recommended to implement the corresponding function in the introspection module.

Function Values

Function values are often represented as function pointers or references in many programming languages. However, some languages do not have first-class functions.

In declarative languages, it is recommended to use a function name as a string to represent a function value, when you can ensure it exists in the same pack written in a programming language, in another pack, or in the host. Oftentimes we just choose to not implement those parts of OBGX in a declarative language :)