PMunch / protobuf-nim

Protobuf implementation in pure Nim that leverages the power of the macro system to not depend on any external tools
MIT License
171 stars 14 forks source link

Export generated code in module #18

Closed qxxxb closed 4 years ago

qxxxb commented 4 years ago

I was wondering if there a way to export code generated from a parseProto call. Currently I can do something like this:

syntax = "proto3";
// client_msg.proto

message ClientMsg {
    enum Kind {
        RequestInfo = 0;
        Connect = 1;
        Disconnect = 2;
        GameInput = 3;
    }

    enum GameInput {
        MoveLeft = 0;
        MoveRight = 1;
        MoveUp = 2;
        MoveDown = 3;
    }

    Kind kind = 1;
    repeated GameInput gameInputs = 2;
}
# client_msg.nim
import protobuf

parseProtoFile("client_msg.proto")

export initClientMsg
export ClientMsgKind
export ClientMsg
export write
export readClientMsg
export has
type GameInput* = ClientMsg_GameInput

In another module, when I use the client_msg module, some things work, and others don't.

# client.nim
import streams, ./client_msg

var msg = initClientMsg(
  kind = ClientMsgKind.GameInput,
  gameInputs = @[GameInput.MoveLeft, GameInput.MoveUp]
)

var stream = newStringStream()
stream.write(msg) # Compile error: type mismatch: got <ClientMsg>

var readMsg = stream.readClientMsg()
if readMsg.has(kind):
  echo readMsg.kind # Compile error: expression '.' cannot be called
PMunch commented 4 years ago

This has to do with how this protobuf implementation masks the fields. If you look at -d:echoProtobuf you can see the code that's actually generated. This code calls the makeDot helper that creates . and .= operators that will verify that the field is actually initialised in the object. I don't quite remember why I didn't use the Option type for this, but it might've been because options can be clunky to work with (at least without using my optionsutils package :)), but it might've been a better reason for it as well.

So the problem here is that you aren't exporting everything that protobuf needs to properly access that objects fields. If you include

export `.`
export `getField`

in your client_msg.nim file it should work fine. You might also want to add a stream.setPosition(0) after your stream.write call however if you actually want to read the object back in ;)

It might be worth adding a exportType macro or template to protobuf that creates a block like this automatically so this process is easier. Or at least a exportProtobuf template that exports the common procedures like ., .=, write, has, etc.

PMunch commented 4 years ago

Created the exportMessage macro to make things easier, https://github.com/PMunch/protobuf-nim#exporting-message-definitions. So your client_msg.nim file can now be simplified as:

# client_msg.nim
import protobuf

parseProtoFile("client_msg.proto")

exportMessage ClientMsg

export ClientMsgKind
type GameInput* = ClientMsg_GameInput
qxxxb commented 4 years ago

Wow thanks! I'll try this out