Godobuf

repository·master·Indexed 16 days ago

https://github.com/oniksan/godobuf

A Google Protocol Buffers compiler that translates .proto files into GDScript files for use in Godot 4.6. It enables the use of protobuf messages as native GDScript classes for data serialization and deserialization, providing both a Godot editor plugin interface and a headless command-line mode.

Tokens
2.8K
Snippets
8
Records
12
Agent score
14%

What's inside godobuf

  1. Work with Oneof fields in GDScript

    master

    A oneof group allows only one field to be set at a time. Setting a new field in the group automatically clears the others.

    For each oneof group, Godobuf generates:

    • set_<field_name>(value) and get_<field_name>() for each member.
    • has_<field_name>(): Returns true if that specific field is set.
    • get_<oneof_name>_case(): Returns the tag number of the currently set field, or 0 if none are set.
    • <OneOfName>Case enum: Used for match statements. It contains <ONEOF_NAME>_NOT_SET = 0 and <FIELD_NAME> = <tag_number> for each field.
    var a = MyProto.A.new()
    a.set_f1("my string")
    
    # Check if a field is set
    if a.has_f1():
        print("F1 is set")
    
    # Use match with the generated Case enum
    match a.get_my_oneof_case():
        a.MyOneofCase.F_1: 
            print("Text is set: ", a.get_f1())
        a.MyOneofCase.F_2: 
            print("Number is set: ", a.get_f2())
        a.MyOneofCase.MY_ONEOF_NOT_SET: 
            print("No payload field set")
  2. Compile .proto files using the Godot User Interface

    master

    You can compile protobuf files directly within the Godot editor using the Godobuf panel:

    Compile a single file

    1. In the Godobuf panel, open the Input protobuf file dialog and select your .proto file.
    2. Open the Output GDScript file dialog to choose a destination directory and filename.
    3. Click Compile single file.
    4. Check the Godot Output console for success or error details.

    Compile a directory of files

    1. In the Directory conversion section of the panel, open Input proto directory and select a folder containing .proto files.
    2. Open Output directory and select the destination.
    3. Click Compile directory.

    Godobuf compiles all files recursively and preserves the relative folder structure in the output directory.

  3. Install the Godobuf plugin in Godot

    master

    To use Godobuf in your Godot project, follow these steps:

    1. Copy the godobuf directory from the addons directory of the Godobuf repository into your Godot project's addons directory.
    2. In the Godot editor, go to Project -> Project Settings.
    3. Select the Plugins tab.
    4. Locate the Godobuf plugin and check the Enabled box.
    5. Close the settings window. A Godobuf panel will appear in the Godot editor (check the last tab if it is not immediately visible).
  4. Serialize and Deserialize Protobuf messages in GDScript

    master

    After generating your .gd files, you can use them in your project by preloading the script and interacting with the generated classes.

    1. Preload the generated script

    const MyProto = preload("res://my_proto.gd")

    2. Pack (Serialization)

    To convert a message object into bytes, instantiate the class and use to_bytes():

    var a = MyProto.A.new()
    a.set_f1(12.554)
    var packed_bytes = a.to_bytes() # Returns PackedByteArray

    3. Unpack (Deserialization)

    To convert bytes back into a message object, instantiate a new object and use from_bytes(). Note: You cannot re-call from_bytes() on the same object instance; you must create a new one.

    Important: Always check the returned result code against MyProto.PB_ERR to ensure successful unpacking.

    var a = MyProto.A.new()
    var result_code = a.from_bytes(my_byte_sequence)
    
    if result_code == MyProto.PB_ERR.NO_ERRORS:
        print("OK")
        var f1 = a.get_f1()
    else:
        print("Error code: ", result_code)
        return
    const MyProto = preload("res://my_proto.gd")
    
    # Serialization
    var a = MyProto.A.new()
    a.set_f1(12.554)
    var packed_bytes = a.to_bytes()
    
    # Deserialization
    var b = MyProto.A.new()
    var result_code = b.from_bytes(packed_bytes)
    if result_code == MyProto.PB_ERR.NO_ERRORS:
        print(b.get_f1())
  5. Manage Map (Dictionary) fields

    master

    Protobuf maps are exposed as GDScript Dictionary objects.

    • Non-message values: Use add_<field_name>(key, value) to append a key-value pair.
    • Message values: Use add_<field_name>(key) to append a key and receive the newly created message instance, which you can then populate using its own setters.
    • Retrieval: Use get_<field_name>() to get the full Dictionary.
    var a = MyProto.A.new()
    
    # Map with scalar values
    a.add_f1(1, "one")
    a.add_f1(2, "two")
    
    # Map with message values
    var b = a.add_f2(10) # Returns the message instance
    b.set_f1(100)
    b.set_f2(200)
    
    # Getting the map
    var my_dict = a.get_f1() # Returns Dictionary
  6. Debug messages with to_string()

    master

    Every generated message class includes a to_string() method. This generates a human-readable debug string containing field names and their current values. Default values are omitted from the output.

    # message is any Protobuf class instance
    print(message.to_string())
  7. Handle Scalar, String, and Bytes types in GDScript

    master

    Godobuf provides generated methods for interacting with basic Protobuf types.

    • Scalar types (int32, uint32, sint32, fixed32, sfixed32, int64, uint64, sint64, fixed64, sfixed64, float, double, bool): Use set_<field_name>(value) to set and get_<field_name>() to retrieve values.
    • Strings: Use set_<field_name>(value) and get_<field_name>() as with scalars.
    • Bytes: Use set_<field_name>(value) (accepts Array or PackedByteArray) and get_<field_name>() which returns a PackedByteArray.
    # Scalar Example
    var a = MyProto.A.new()
    a.set_f1(12.554)
    a.set_f2(500)
    var val = a.get_f1()
    
    # String Example
    a.set_f1("my string")
    var s = a.get_f1()
    
    # Bytes Example
    a.set_f1([1,2,3,4,5])
    a.set_f2(PackedByteArray([0,3,4,5,7]))
    var b = a.get_f1() # Returns PackedByteArray
  8. Use Enums in GDScript

    master

    Enums generated by Godobuf follow a specific naming hierarchy based on the message structure: <preloaded/loaded resource instance name>.<class root>.<class inner>.<enum name>.

    To set an enum value, use set_<field_name>(value) where the value is a member of the generated enum. To retrieve it, use get_<field_name>().

    # Assuming MyProto is the loaded resource
    var a = MyProto.A.new()
    
    # Setting values
    a.set_f1(MyProto.TestEnum.VALUE_1)
    a.set_f2(MyProto.B.BEnum.BVALUE_2)
    
    # Getting and checking values
    var my_field_f1 = a.get_f1()
    if my_field_f1 == MyProto.TestEnum.VALUE_1:
        print("OK value-1")
  9. Handle Message and Repeated fields

    master

    Nested Messages

    To set a nested message field, use new_<field_name>(). This method creates the object instance, assigns it to the field, and returns the instance for immediate configuration.

    Repeated Fields

    Repeated fields are exposed as GDScript Array objects.

    • Non-message types: Use add_<field_name>(value) to append to the array.
    • Message types: Use add_<field_name>() to create a new instance, append it to the array, and return it for configuration.
    • Retrieval: Use get_<field_name>() to get the Array.
    # Nested Messages
    var a = MyProto.A.new()
    var c = a.new_Af2() # Creates and returns instance of C
    c.set_Cf1("my string")
    
    # Repeated Fields
    var r = MyProto.R.new()
    r.add_Af1(10) # Scalar repeated
    r.add_Af1(20)
    
    var b = r.add_Af2() # Message repeated
    b.set_Bf1(100)
    
    # Getting values
    var my_array = r.get_Af2() # Returns Array
  10. Unpack result codes (PB_ERR)

    master

    The from_bytes() method returns an integer representing the status of the unpacking operation. Use these codes to handle errors:

    CodeNameDescription
    0NO_ERRORSSuccess
    -1VARINT_NOT_FOUNDParse error: Byte sequence does not contain a varint attribute described in .proto
    -2REPEATED_COUNT_NOT_FOUNDParse error: Byte sequence contains negative size of repeated block
    -3REPEATED_COUNT_MISMATCHParse error: Byte sequence size is less than field type size or block size
    -4LENGTHDEL_SIZE_NOT_FOUNDParse error: Byte sequence contains negative size of length delimited field type
    -5LENGTHDEL_SIZE_MISMATCHParse error: Byte sequence size is less than length delimited field type size
    -6PACKAGE_SIZE_MISMATCHParse error: Byte sequence size is less than package required length
    -7UNDEFINED_STATEUnspecified error
    -8PARSE_INCOMPLETEByte sequence is correct but incomplete (e.g., package did not arrive completely)
    -9REQUIRED_FIELDSProtobuf v2 error: Not all required fields are filled in the byte sequence
  11. Mapping of Protobuf types to GDScript

    master

    When Godobuf generates GDScript code, it maps Protobuf data types to the following GDScript types:

    ProtobufGDScriptGDScript typeof
    int32, uint32, sint32, fixed32, sfixed32, int64, uint64, sint64, fixed64, sfixed64intTYPE_INT
    floatdouble / real (Note: unpacking results in single precision)TYPE_REAL
    doubledouble / realTYPE_REAL
    boolboolTYPE_BOOL
    enumenum / intTYPE_INT
    stringStringTYPE_STRING
    bytesPackedByteArrayTYPE_PACKED_BYTE_ARRAY
    oneoffields described in oneof (not grouped into a structure)different
    mapDictionaryTYPE_DICTIONARY
    messageclassTYPE_OBJECT
    repeated fieldsArrayTYPE_ARRAY
  12. Compile .proto files from the Command Line

    master

    You can run the Godobuf compiler headlessly from your project's root directory using the Godot executable. This is useful for CI/CD or automated workflows.

    Run the following command (replace A.proto and my_proto.gd with your actual files):

    godot4 --headless -s addons/godobuf/godobuf_cmdln.gd --input=A.proto --output=my_proto.gd

    To simplify usage, you can define an alias:

    alias godobuf='godot4 -s addons/godobuf/godobuf_cmdln.gd'
    godot4 --headless -s addons/godobuf/godobuf_cmdln.gd --input=A.proto --output=my_proto.gd