- Added support for Godot 4
- Fixed bug with return type in add method for map
- Implemented unit tests
- Fixed repeat field clearing bug
- Fixed map bug with default values
Current Godobuf master branch used for Godot version 4.0.
If you want use Godobuf plugin for other Godot versions see:
- 3.0.x use branch tagged v0.2.2 https://github.com/oniksan/godobuf/tree/v0.2.2
- 3.1.x use branch tagged v0.3.0 https://github.com/oniksan/godobuf/tree/v0.3.0
- 3.5.x use branch tagged v0.5.0 https://github.com/oniksan/godobuf/tree/v0.5.0
Godobuf is a Google Protocol buffers compiler from .proto-file description to GDScript output file.
Output file contains user protobuf messages represented by classes and protobuf core code which responsible for data serialization/deserialization.
Godobuf used as plugin for Godot v4.0
Godobuf is easy to use, does not require rebuilding the Godot, because it is written in GDScript. All you need is to start the plugin.
- Protobuf v2 and v3
- Message definitions (cascade messages)
- Simple Protobuf types (int32, uint32, sint32, fixed32, sfixed32
int64, uint64, sint64, fixed64, sfixed64, float, double, bool, enum, string, bytes) - Oneof type
- Map type
- Repeated fields
- Option
packed
for fields - Protobuf imports (also public modificator)
- Debug string for class (message) fields (since v0.2.1)
- Packages
- Reserved statements
- Services
Any
types- Default values option for fields
- Create a new project in Godot or use an existing project.
- Copy
addons
directory from Godobuf repository to your Gotot project directory. - Choose in Menu
Project->Project Settings
.
- In
Project Settings
window choosePlugins
tab. - Find Godobuf plugin and set status
Active
.
- Close
Project Settings
window. - The Godobuf panel will be displayed in the Godot.
- Please note that the Godobuf panel may not be visible, because located on the last tab.
- Open file dialog window
Input protobuf file
. - Choose *.proto file in a dialog window.
- Open file dialog window
Output GDScript file
. - Choose directory and enter output file name.
- Press
Compile
button. - See Godot
Output
for details. - All possilble errors details are displayed in Godot
Output
- After task completion you'll see alert window which report you about compilation result.
- If compilation was successful a new GDScript will be created in a specified directory.
- Use script in you project.
- From the root folder of your project, run
godot --headless -s addons/protobuf/protobuf_cmdln.gd --input=A.proto --output=my_proto.gd
- Optionally, define an alias:
alias godobuf='godot -s addons/protobuf/protobuf_cmdln.gd'
- This option is mainly for those who modify the code or want to check the stability of the version.
- Press
Run unit tests (proto 2)
orRun unit tests (proto 3)
button to start the tests. - See Godot
Output
for details. - All possilble errors details are displayed in Godot
Output
- After task completion you'll see alert window which report you about tests result.
Protobuf | GDScript | GDScript typeof |
---|---|---|
int32, uint32, sint32, fixed32, sfixed32 int64, uint64, sint64, fixed64, sfixed64 |
int | TYPE_INT |
float | double / real note: number after unpacking will be single precision |
TYPE_REAL |
double | double / real | TYPE_REAL |
bool | bool | TYPE_BOOL |
enum | enum / int | TYPE_INT |
string | String | TYPE_STRING |
bytes | PoolByteArray | TYPE_RAW_ARRAY |
oneof | fields described in oneof note: fields are not grouped into a structure, but you can set the value for only one field |
different |
map | Dictionary | TYPE_DICTIONARY |
message | class | TYPE_OBJECT |
repeated fields | Array | TYPE_ARRAY |
Initially all fields setted as null.
Default values in .proto syntax not supported.
Repeated fields are setted as empty Array.
Initially all fields setted to default values specified in Protobuf manual.
Repeated fields are setted as empty Array.
Keywords cannot be used in any other constructions for which they are not intended. In particular, keywords cannot be used as field names and message names.
message, double, float, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, string, bytes, enum, map, oneof, true, false, syntax, import, package, option, service, reserved, weak, public, repeated, required, optional, allow_alias, custom_option, extensions, packed
- Open the script in which Protobuf will be used.
- Preload previously generated script:
const MyProto = preload("res://my_proto.gd")
- Use previously described messages as GDScript classes. Example, we have .proto file:
syntax = "proto3";
message A {
double f1 = 1;
B f2 = 2;
}
Packed message must be instanced as GDScript class object directly.
Pack message A:
# Create packed class (message)
var a = MyProto.A.new()
# Example, set field f1
a.set_f1(12.554)
# Pack message A
# Use to_bytes() method, it's return PoolByteArray
var packed_bytes = a.to_bytes()
Unpacked message must be instanced as GDScript class object directly.
Re-calling from_bytes() of the same object instance is not allowed. You must create new object instance.
Unpack message A:
# Create unpacked class (message)
var a = MyProto.A.new()
# Unpack byte sequence to class (message) A.
# Use from_bytes(PoolByteArray my_byte_sequence) method
var result_code = a.from_bytes(my_byte_sequence)
# result_code must be checked (see Unpack result codes section)
if result_code == MyProto.PB_ERR.NO_ERRORS:
print("OK")
else:
return
# Use class 'a' fields. Example, get field f1
var f1 = a.get_f1()
from_bytes() method returns a success or an error state.
User must check the result code to make sure the unpacking is correct (see 'Unpack message' section above).
Result codes (enum PB_ERR) are presented in table below.
Unpack result code | int representation | Description |
---|---|---|
NO_ERRORS | 0 | Success |
VARINT_NOT_FOUND | -1 | Parse error. Byte sequence does not contains varint attribute, but varint is described in .proto file. |
REPEATED_COUNT_NOT_FOUND | -2 | Parse error. Byte sequence contains negative size of repeated block. |
REPEATED_COUNT_MISMATCH | -3 | Parse error. Byte sequence size less than field type size or block size. |
LENGTHDEL_SIZE_NOT_FOUND | -4 | Parse error. Byte sequence contains negative size of length delimited field type. |
LENGTHDEL_SIZE_MISMATCH | -5 | Parse error. Byte sequence size less than length delimited field type size. |
PACKAGE_SIZE_MISMATCH | -6 | Parse error. Byte sequence size less than package required length. |
UNDEFINED_STATE | -7 | Error. Unspecified error. |
PARSE_INCOMPLETE | -8 | Byte sequence error. Byte sequence is correct and corresponds to the .proto descriptor, but it's size is incomplete. The package (byte sequence) did not come completely. |
REQUIRED_FIELDS | -9 | Sender error. For Protobuf v2. Byte sequence is correct and corresponds to the .proto descriptor, but not all required fields are filled (data is missing in the byte sequence). |
Scalar types: int32, uint32, sint32, fixed32, sfixed32, int64, uint64, sint64, fixed64, sfixed64, float, double, bool.
Use method set_<field_name>(value)
. Sets field value.
Use method get_<field_name>()
. Returns field value.
.proto-file
syntax = "proto3";
message A {
double f1 = 1;
int32 f2 = 2;
}
GDScript. Using set
var a = MyProto.A.new()
a.set_f1(12.554)
a.set_f2(500)
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_field_f1 = a.get_f1()
var my_field_f2 = a.get_f2()
Use similarly to scalar types.
Use method set_<field_name>(value)
. Sets field value.
Use method get_<field_name>()
. Returns field value.
.proto-file
syntax = "proto3";
message A {
string f1 = 1;
}
GDScript. Using set
var a = MyProto.A.new()
a.set_f1("my string")
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_field_f1 = a.get_f1()
Use similarly to scalar types.
Use method set_<field_name>(value)
. Sets field value.
Use method get_<field_name>()
. Returns field value as PoolByteArray.
.proto-file
syntax = "proto3";
message A {
bytes f1 = 1;
bytes f2 = 2;
}
GDScript. Using set
var a = MyProto.A.new()
# Correct both
a.set_f1([1,2,3,4,5])
a.set_f2(PoolByteArray([0,3,4,5,7]))
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_pool_byte_array_f1 = a.get_f1()
Use similarly to scalar types, but values should be taken from Enum which was generated by compiler.
Generated Enums are named according to the following rule:
<message root>.<message inner>. ... <enum name>
Using enum name in GDScript:
<preloaded/loaded resource instance name>.<class root>.<class inner>. ... <enum name>
Use method set_<field_name>(value)
. Sets field value.
Use method get_<field_name>()
. Returns field value.
.proto-file
syntax = "proto3";
enum TestEnum {
VALUE_0 = 0;
VALUE_1 = 1;
VALUE_2 = 2;
}
message B {
enum BEnum {
BVALUE_0 = 0;
BVALUE_1 = 1;
BVALUE_2 = 2;
}
}
message A {
TestEnum f1 = 1;
B.BEnum f2 = 2;
}
GDScript. Using set
var a = MyProto.A.new()
a.set_f1(MyProto.TestEnum.VALUE_1)
a.set_f2(MyProto.B.BEnum.BVALUE_2)
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_field_f1 = a.get_f1()
if my_field_f1 == MyProto.TestEnum.VALUE_1:
print("OK value-1")
Use similarly to scalar types, but you can set the value for only one field grouped in oneof.
Oneof group name not used in GDScript.
If you set one field of oneof group another fields will be cleared automatically.
Use method set_<field_name>(value)
. Sets field value.
Use method get_<field_name>()
. Returns field value.
Use method has_<field_name>()
. Returns true if value is been setted in byte_sequence.
.proto-file
syntax = "proto3";
message A {
oneof my_oneof {
string f1 = 1;
int32 f2 = 2;
map<int32, int32> f3 = 3;
}
}
GDScript. Using set
var a = MyProto.A.new()
a.set_f1("my string")
# if you set f2: f1 and f3 will be setted to default values (or null for Protobuf v2)
a.set_f2(10)
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_field_f1 = a.get_f1()
GDScript. Using has
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
if a.has_f1():
print("F1")
elif a.has_f2():
print("F2")
For non-message map values used method:
add_<field_name>(key, value)
. Append one key-value pair to Dictionary (map) field.
If map value is message (class) used method:
add_<field_name>(key)
. Append one key-value pair to Dictionary (map) field and return value object instance.
Use method get_<field_name>()
. Returns GDScript Dictionary (map).
.proto-file
syntax = "proto3";
message A {
message B {
int32 f1 = 1;
int32 f2 = 2;
}
map<int32, string> f1 = 1;
map<int32, B> f2 = 2;
}
GDScript. Using add
var a = MyProto.A.new()
a.add_f1(1, "one")
a.add_f1(2, "two")
a.add_f1(4, "four")
var b
b = a.add_f2(10)
b.set_f1(100)
b.set_f2(200)
b = a.add_f2(20)
b.set_f1(1000)
b.set_f2(2000)
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_dict_f1 = a.get_f1()
Use method new_<field_name>()
. Create object instance, sets as field value and returns instance.
Use method get_<field_name>()
. Returns object instance.
.proto-file
syntax = "proto3";
message A {
message B {
int64 Bf1 = 1;
float Bf2 = 2;
}
message C {
string Cf1 = 1;
B Cf2 = 2;
}
int32 Af1 = 1;
C Af2 = 2;
}
GDScript. Using new (set)
var a = MyProto.A.new()
a.set_Af1(10)
var c = a.new_Af2()
c.set_Cf1("my string")
var b = c.new_Cf2()
b.set_Bf1(20)
b.set_Bf2(2.5)
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_class_instance_c = a.get_Af2()
var my_int32 = my_class_instance_c.get_Af1()
# ...
Use method add_<field_name>(value)
. Append value to Array.
Use method add_<field_name>()
. Create object instance, sets as next Array element and returns instance.
Use method get_<field_name>()
. Returns Array of fields.
.proto-file
syntax = "proto3";
message A {
message B {
int64 Bf1 = 1;
float Bf2 = 2;
}
repeated int32 Af1 = 1;
repeated B Af2 = 2;
}
GDScript. Using add
var a = MyProto.A.new()
a.add_Af1(10)
a.add_Af1(20)
a.add_Af1(30)
var b
b = a.add_Af2()
b.set_Bf1(100)
b.set_Bf2(1.5)
b = a.add_Af2()
b.set_Bf1(200)
b.set_Bf2(2.5)
GDScript. Using get
var a = MyProto.A.new()
var state = a.from_bytes(byte_sequence)
# First you have to check the 'state' ...
var my_array_b = a.get_Af2()
for inst in my_array_b:
print(inst.get_Bf1())
All message classes have to_string() method.
to_sting() generate debug string, which contains field names and their values. Default values are ignored.
field_<type>
- field name
field_int: 12;
field_float: 12.34;
field_bool: True;
field_string: "test";
field_enum: ENUM::2;
field_message_1: {
field_message_2: {
field_int: 12;
field_float: 12.34;
}
field_bool: True;
field_string: "test";
};
field_repeated: [
0: {
field_int: 123;
field_bool: True;
},
1: {
field_int: 454;
field_bool: False;
}
];
field_repeated: [
1: 2,
2: 3,
3: 4
];
field_map: (
"key1": "value1",
"key2": "value2"
);
field_map: (
"object1": {
field_int: 123;
field_bool: True;
},
"object2": {
field_int: 454;
field_bool: False;
}
);
field_bytes: <1, 2, 4, 5>;
field_empty_message: {};
# Debug print, where "message" - any Protobuf class (message)
print(message.to_string())
Google Protobuf: https://developers.google.com/protocol-buffers/docs/overview
Godot engine: http://godotengine.org
Godot docs: https://docs.godotengine.org/en/stable/
Godobuf test suites: https://github.com/kittenseater/godobuf-test
https://github.com/kittenseater/godobuf-gen-v2
https://github.com/kittenseater/godobuf-gen-v3