npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mtvproject/rpc

v1.0.1

Published

base component

Readme

@mtvproject/rpc

Main engine of change

@mtvproject/rpc was the first repository we build to create the memetaverse explorer (the game client and SDK). It is probably the only thing that is left from that time, even though it passed the test of time very well we often make suboptimal decisions because the way it works. It is time to start reconsidering what things we have been sacrificing, even without noticing, that may be of a greater value in the future 5 years of Memetaverse.

This is a non-exhaustive list of considerations for this new protocol:

  • Most of the time, it will be a in-memory transport, that is, communication between processes or threads. Not over a network wire.
  • While we use an in-browser approach for the Explorer, browser optimizations must be taken into account
    • Transferable objects between Workers and Renderer threads, SharedArrayBuffers when possible.
    • Not serialize everything to JSON back-and-forth
    • Remove stacked layers, optimize function calls and object allocations
    • Copying slices of ArrayBuffers is heavily optimized in browsers
  • Unity will only care about ArrayBuffers written to its memory, there will be no need to use JSON in the main threads
  • We may want to run several scenes in the same worker, multiplexing should be part of the initial design.
  • To maintain a healthy anti corruption layer (ACL), any kind of application should find the the RPC library useful. Not only the memetaverse Explorer and scenes.

Design requirements

  • Notifications will evolve into streams, nowadays subscribing to an event is difficult to reason and hard to code
  • RPC Definition may leverage GRPC code generation to document the protocol itself
  • Remote procedures must be initialized by requesting a module
  • Module-based remote calls did prove themselves useful with time, to organize the remote calls we may continue using modules. Rename classes to modules.

Notice

  • Even though this document will be written using protocol buffers, the implementation may use a smaller and faster hand-made protocol for performance.
  • Message payloads may or may not use protocol buffers

Start a RPC session and load a module to call a remote procedure

participant Client as C
participant Server (Remote) as S
note over C: Port lifecycle
C->C: createPort(name)
C->S: CreatePort {message_id,name}
S->S: createPortAdapter(port_id, name)\nConfigure modules for the named port
S->C: CreatePortResponse {message_id,port_id}
note over C: Modules lifecycle
C->S: LoadModule {port_id,module_name}
S->C: LoadModuleResponse {port_id,module_id,procedures[]}
C->C: Create wrapper for module_id\nusing all the exposed procedures

RPC messages

The RPC communications resemble JSON-RPC, the main differences are:

  • The method field is now called procedure_id (and it is a number)
  • module_id was removed
  • payload can be anything (serialized)

Regular RPC call from Scene to Kernel

participant Scene (client) as C
participant Kernel (server) as S
C->S: Request {procedure_id, payload}
S->C: Response {message_id, payload}

Getting an async stream (closed by client)

participant Scene (client) as C
participant Kernel (server) as S
C->S: Request {message_id}
S->C: Response {message_id,streaming=true,seqId=0}
C->C: Generate async iterator for {message_id}
C->S: StreamMessage {ack=true,message_id,seqId=0}
note over C: Ask for a new item to be generated using ack=true
S-->C: StreamMessage {message_id,payload,seqId=1}
C->S: StreamMessage {ack=true,message_id,seqId=1}
note over C: Close the message by responding\nthe last ACK with ack=true,closed=true
S-->C: StreamMessage {message_id,payload,seqId=2}
C->S: StreamMessage {ack=true,message_id,seqId=2,closed=true}
S->S: Close async Generator
C->C: Close async Iterator
S-->C: StreamMessage {message_id,closed=true}
C->C: Close async iterator

Getting an async stream (closed by server)

The server will send a special StreamMessage with a new SeqId to tell the client that a stream (generator) was closed

Implementation of the interfaces

Nowadays the current RPC uses classes in the server side to keep track of the methods that can be called. The classes are requested from the client using a similar mechanism as the previously defined, we now call them modules. The classes used to implement a special interface in order to comply with the library, the semantics and the conventions of those interfaces are hard to get while reading the code. The approach of this new library will be more "verbosic" in order to allow the developers to understand what happens behind the curtain, and placing them in control of the combinatorial semantics of the library.

Simplest use case - Remote jobs

// worker.ts

import { createRpcRpcClient } from "@mtvproject/rpc"

const rpcClient = createRpcClient(WebSocketTransport("wss://server:1234"))
const client = await rpcClient.createPort("scene")

const jobsRpc = await client.loadModule("JobQueue")

for await (const job of jobsRpc.getJobs()) {
  const result = await doJob(job)
  await jobsRpc.sendJobResult(getSuccessMessagePayload(job, result))
}
// server

import {createRpcServer} from '@mtvproject/rpc'

// create the rpcServer to start handling transports
const rpcServer = createRpcServer()

rpcServer.onPort(port => {
    if (port.name == 'scene') {
        port.registerModule('JobQueue', createJobQueue)
    }
})

async function createJobQueue(port) {
    return {
        // ================================================
        // Send jobs
        // ================================================
        async * getJobs(workerPort) {
            while (true) {
                yield async getNextJob()
            }
        },
        // ================================================
        // Receive resolved jobs
        // ================================================
        async sendJobResult(workerPort, payload) {
            // do something with payload
        }
    }
}

WebSocketServerTransport(1234).onConnection(transport => {
    rpcServer.addTransport(transport)
})

Advanced use cases


server.registerModule('EngineModule', {
    // =====================================================================
    // Advanced use case: Subscribe to a tick, happens once every 16ms aprox
    // =====================================================================
    async * getTickStream(sceneWorker) {
        let i = 0

        while (true) {
            await sleep(16)
            if (yield i++) {
                // stop if the channel is closed
                break
            }
        }
    }
})

server.registerModule('InputModule', {
    // ============================================
    // Advanced use case: Subscribe to input events
    // ============================================
    async * getInputEventStream(sceneWorker) {
        while (true) {
            // https://stackoverflow.com/questions/51045136/how-can-i-use-a-event-emitter-as-an-async-generator
            const asyncStream = new AsyncStream()

            const observer = inputEvents.on('input', event => {
                asyncStream.push(serialize(event))
            })

            for async (let event in asyncStream) {
                if (yield event) {
                    // stop if the channel is closed
                    break;
                }
            }

            // release the observer
            inputEvents.off(observer)
        }
    }
})

Design considerations

  1. The transport is the abstraction (represents the media layer)
  2. Every packet has a port_id, to route the message to its correspondent instances (session layer, cluster management)
    1. Default cluster coordinator uses port_id = 0
  3. Every message sends a blob payload. Code may be generated to understand the blob (application layer)

Semantics of stream messages

  • Streams share the same messageId
  • Every message of the stream has an incremental sequenceId
  • Streams have ACKs for each sequenceId*messageId
  • The ACK message is an algebraic type: Ack(messageId,sequenceId) and AckClose(messageId,sequenceId)
  • Messages of the stream are implemented using generators in mind, that is to support backpressure and to avoid network congestion
  • The first message of a stream also carries the first element of the stream
  • The AckClose(messageId,sequenceId) message carries no element, it is emitted right after the last element in the stream