golang / protobuf

Go support for Google's protocol buffers
BSD 3-Clause "New" or "Revised" License
9.79k stars 1.58k forks source link

Slice of generated message structs is both empty and not empty at the same time #1645

Closed chrispytoes closed 2 months ago

chrispytoes commented 2 months ago

I'm really sorry for the bad title but I really have no idea what's happening here, none of this makes any sense.

I am new to protobuf, and I am having trouble with the generated message types. I am trying to prepare messages in one goroutine, put them in a channel, and collect them in another goroutine to package them into the final message to send back.

entity := &protobuf.EntityCreate{
    Id:       player.ID(),
    Type:     protobuf.EntityType_PLAYER,
    X:        player.X,
    Y:        player.Y,
    Rotation: player.Rotation,
}

fmt.Println(entity)  // This logs a blank line
fmt.Println(*entity) // This logs the data

g.EntityCreates <- entity

However when I pull the messages out of the channel to put them into a slice for the final message, the slice is still empty (but still has length of 1):

update := &protobuf.FrameUpdate{
    EntityCreates: make([]*protobuf.EntityCreate, len(g.EntityCreates)),
}

fmt.Println(len(g.EntityCreates)) // This correctly logs a 1, channel definitely has data
for i := range len(g.EntityCreates) {
    update.EntityCreates[i] = <-g.EntityCreates
    fmt.Println(len(g.EntityCreates)) // This logs a 0, so data definitely got read into the slice
}

fmt.Println(len(update.EntityCreates)) // This logs a 1, great!
fmt.Println(update.EntityCreates)      // This logs empty brackets: "[]" (not great)
fmt.Println(update.EntityCreates[0])   // This crashes the program with an index out of range error (how???)

data, err := proto.Marshal(update)
if err == nil {
    err = c.WriteMessage(websocket.BinaryMessage, data)
}

Finally, when I parse these messages out on the receiving end, I get the FrameUpdate object but the EntityCreates array inside is still empty.

This is my protoc command: protoc --experimental_allow_proto3_optional --go_opt=paths=source_relative -I=./proto-schema --go_out=./proto ./proto-schema/messages.proto

puellanivis commented 2 months ago

Small side note: style is to rename your protobuf package import into pb (if you import more than one, then some sort of abbreviation + pb, so like field_office_operations_proto would be foopb), to reduce the number of characters of a sequence that usually ends up appearing in a lot of the code.

Could you provide the actual stacktrace from the panic when you access update.EntityCreates[0]?

What are the values you’re feeding into &protobuf.EntityCreate{}? Any of the values that are the zero value, will be omited from the prototext render you’ll get from fmt.Sprint(&pb.FooMessage{}), and if all the values are zero values, then it should end up being an empty string. If you could share the results from the fmt.Println(*entity) that would help.

Adding onto that last paragraph, it’s recommended to make your first Enum value INVALID or UNKNOWN or some other value that is clearly not valid data. Since this will be the zero value, it’s the value that won’t (by default) show up in prototext or protojson. It’s best when that omitted value is an invalid value, rather than a valid value.

In the end, I’m pretty sure based on the symptoms you describe that you’re running into “zero values are omitted from marshalled data”. But without the above extra details it’s hard to be 100% sure.

but the EntityCreates array inside is still empty.

Does it have len(EntityCreates) == 0 or when you print it out it’s [] empty?

chrispytoes commented 2 months ago

@puellanivis Okay, we may be getting somewhere because I didn't know zero values were excluded when printing. Yes all of the data was default zero values in this particular case. However, I just tested again hardcoding in some non-zero values, and while the data does actually print now when I log the struct, the client still gets an empty array.

The client receiving this data is in JavaScript, so it's pretty clear there that it's an empty array, and it is still getting an empty array even when sending the message with non-zero data.

With the non-zero data hardcoded in, if I print the update variable right before marshaling, it does print data now:

update := &pb.FrameUpdate{
    EntityCreates: make([]*pb.EntityCreate, len(g.EntityCreates)),
}

for i := range len(g.EntityCreates) {
    update.EntityCreates[i] = <-g.EntityCreates
}

fmt.Println(update) // This does print the expected data now

data, err := proto.Marshal(update)
if err == nil {
    err = c.WriteMessage(websocket.BinaryMessage, data) // Client receives and parses this message, but still, `entityCreates` is empty
}
chrispytoes commented 2 months ago

The issue was with my client, I was not decoding the message correctly and it wasn't giving me an error to indicate that. However the fact that zero values were not printing did throw me off in thinking the issue was with the server.