Wave-Play / robo.js

Power up Discord with effortless activities, bots, servers, and more! ⚡
https://robojs.dev
MIT License
39 stars 18 forks source link

@robojs/sync - `useSyncBroadcast` and `useSyncContext` hooks #329

Open Pkmmte opened 4 days ago

Pkmmte commented 4 days ago

These new hooks are intended to bring about a new Context System.

Essentially, each namespace (dependency array) may have a context, not just state. It can be used to listen to client events connected to the same key. Since we'd have all clients in the same context, that would also allow us to broadcast arbitrary payloads.

Context System

The existing useSyncState would be modified to return a third variable:

const [status, setStatus, statusContext ] = useSyncState('winning', ['status'])

The statusContext object would include the following fields:

Proposed type signature:

interface SyncContext<ClientData = unknown> {
  clients: Client<ClientData>[]
  clientId: string
  isHost: boolean
  metadata?: Record<string, unknown>
  on?: (event: string, (client: Client<ClientData>) => void) => void
}

interface Client<ClientData = any> {
  id: string
  data?: ClientData // e.g., user info
}

Example usage:

import { useSyncState } from '@robojs/sync'

export function MultiplayerGame() {
  const [gameState, setGameState, syncContext] = useSyncState(initialGameState, ['gameRoom'])
  const { clients, clientId, isHost, broadcast } = syncContext

  // Use the context to implement custom logic
  useEffect(() => {
    if (isHost) {
      // Perform host-specific initialization
    }
  }, [isHost])

  const handlePlayerAction = (action) => {
    // Update the game state locally
    setGameState((prev) => ({ ...prev, ...action }))

    // Broadcast the action to other clients
    broadcast(action)
  }

  return (
    <div>
      <GameBoard state={gameState} onAction={handlePlayerAction} />
      <div>Connected Players: {clients.length}</div>
    </div>
  )
}

useSyncBroadcast

This new hook would allow receiving and sending direct broadcasts to other clients in the same dependency array, or both.

It returns a context object and accepts a function as the first argument. The callback receives the payload and context as arguments. Think of it as a useEffect without the boilerplate.

const { broadcast, context, send } = useSyncBroadcast((message: string, context) => {
  console.log(context.client.id, 'said', message)
}, ['status'])

Result object includes:

useSyncContext

Can be used to get the context of a dependency array easily.

const context = useSyncContext(['status'])

It can optionally accept an object as an optional first item for convenience callbacks:

useSyncContext({
  onConnect: (client) => console.log(client.id, ‘has left’),
  onDisconnect: (client) => console.log(client.id, ‘has joined’)
}, [‘status’])
Rishi-0007 commented 1 day ago

Hi @Pkmmte,

I’d love to work on this issue. Could you please assign it to me? As I’m new to this repository, I’d also appreciate any guidance to help me get started.

Pkmmte commented 18 hours ago

Hi @Rishi-0007

Thank you for your interest in contributing to Robo! This issue was initially tagged as a good first issue, but we then found it involves some deeper integrations that might be challenging as an initial task. In particular due to the way our server and core framework interact with this plugin's websocket connections.

I recommend looking at issue #331 instead, which could be a great place to start. It's more tailored to new contributors and should help you get acquainted with our codebase. I’ve added more details on the exact file to modify and am here (and on Discord) to provide any guidance you need to get started.

Feel free to ask any questions!