Is _radiant.call(...) the only way to communicate between the client and the server?

Looking at base game client services, whenever they want to interact with a server service they do it through call handlers via _radiant.call or _radiant.call_obj. That’s cool, I can do that. But what about the other direction? Do I have to set up "endpoint": "client" functions to communicate from the server to my client service? I feel like events should be able to do this, but I can’t seem to listen to events on my client service that were triggered by the server, even when the object they’re associated with is radiant.

The server can’t easily call the clients, but the client can “trace” data (on datastores, either created manually, or more typically the ones created automatically for controllers/components (their _sv member)), and you can signal events by changing values on the server.

Hmm. It sounds like I need a server service to keep tabs on all the data I’ll want my client service to be able to access, and then I can set up call handlers to access the data (perhaps just a single call handler that returns a datastore). And then I’ll need to partition that data on the server service by player_id and have the client specify player_id in its data request. At which point I’m thinking I’m better off just keeping it as a single server service and partitioning the saved variables by player_id and including player_id in all get/set data calls.

What you describe is a reasonable way to do it, should you want to go down that path.