boardgameio / boardgame.io

State Management and Multiplayer Networking for Turn-Based Games
https://boardgame.io
MIT License
10.03k stars 709 forks source link

Improve documentation about plugins #594

Open adngdb opened 4 years ago

adngdb commented 4 years ago

Hi there,

Not sure if this is the best place to ask questions, so if there's a better one please point me there.

I'm trying to use plugins, and am failing hard. I'm making a few observations that I don't understand, thus I'm not sure if they are bugs or features. The documentation about plugins is quite terse, so hopefully those questions will help improving it afterwards (I'll be happy to open a PR for that).

  1. I cannot access ctx.events from inside my plugin, thus my plugin's api cannot advance the game state.
  2. I cannot access my plugin's data in client-side React components, ctx.myplugin is undefined.
  3. I don't understand how the data works. I've looked at the PlayerPlugin for reference, and it seems it copies the plugin's data in the api call, the exposes that data, then in flush it returns the data exposed by api. That seems overly complex to me (and is not explained in the docs). I've thus tried to simply use data and have flush return that, and it seems to work fine. Any reason not to do that?

Once again, I can provide code examples if needed.

Thanks!

PS: I'm naming this "Improve documentation about plugins" because that's the outcome that I'd like to see here, and I'll do my best to make it happen. :-)

nicolodavis commented 4 years ago

The plugin API is very much in flux, and I didn't want to document it too much until it settles down a bit. Let me add some background about plugins, which should hopeful answer some of your questions (and we can add some of this info to the docs as well):

Each plugin gets its own private state

If you have plugins A and B, the game state is initialized to:

{
  G: {},
  ctx: {},
  plugins: {
    A: { data: {} },
    B: { data: {} },
  }
}

The setup function initializes data

This is probably not that confusing, despite the terseness of the docs.

api creates an ephemeral object (that is not persisted)

The game state looks like this after applying the api functions from all the plugins:

{
  G: {},
  ctx: {},
  plugins: {
    A: { data: {}, api: {} },
    B: { data: {}, api: {} },
  }
}

These new ephemeral objects are made available in ctx as ctx.A and ctx.B. The intention is that the user will interact with these objects, and it will update its own in-memory state in response to that.

flush then persists any necessary data back into data

This completes one cycle. This whole circus is to ensure that api does not get persisted, and it allows providing the user with a nice object in ctx to interact with.

Specific Questions

I cannot access ctx.events from inside my plugin, thus my plugin's api cannot advance the game state.

The plugins are intended to be independent. events and random are also plugins, so you shouldn't be using values from there in your plugins (i.e. this is not a supported use-case).

If you do want to advance the game state in your plugin, you'll have to copy over the code from the events plugin, which uses the undocumented and dangerous flushRaw (use this only if you understand the internals of boardgame.io really well).

I cannot access my plugin's data in client-side React components, ctx.myplugin is undefined.

If you look at the state object at the top of this comment, that should show you how to access plugin data:

client.getState().plugins.A.data   // for example
delucis commented 4 years ago

For React, you should see the plugins data in props:

function Board(props) {
  return (
    <pre>{JSON.stringify(props.plugins.A.data)}</pre>
  );
}
adngdb commented 4 years ago

Thanks for the answers and help!

Regarding the state / data thing, can you confirm there is nothing wrong with using the data object passed to api as your state, and then have flush simply return the same data object? As far as I understand (and as far as my tests go) this works fine. If that's the case, I would even encourage you to make that the default use case, and make flush optional, as that was what was intuitive to me.

The plugins are intended to be independent. events and random are also plugins, so you shouldn't be using values from there in your plugins (i.e. this is not a supported use-case).

I understand why you would want that to be the case, but I will argue that events and random are too much core to the engine to be treated as regular plugins. I see plugins as a very elegant way to organize code and make bits sharable / reusable, but not being able to access core functionalities of the engine makes them a lot less useful to me. I also think that it will be very confusing to developers as, once again, events and random are core to boardgame.io.

nicolodavis commented 4 years ago

One way we can resolve this is to have data from plugins initialized earlier be available to plugins that are initialized later. Then, we just need to guarantee that events and random are processed before any user supplied plugins (this is already true).

adngdb commented 4 years ago

That's interesting, being able to build plugins on top of other plugins. Might be too much power though? I'm afraid it might lead to difficulties for programmers because you have to be aware of the plugins loading order when building your plugin.

Anyway, if that's simpler to do than treating a few "system" plugins as exceptions, I personally don't mind. :)