cchat/v2.md

8.7 KiB

Draft

Identifier

Identifier is the base interface for anything that has an ID. The unique scope of the ID is dependent on whether or not the object is a Service or Interface.

IDs being defined in this draft is only for the purpose of memory management. A struct can have IDs, and these IDs can be used manually. However, since they're not stored in a registry, they're irrelevant to this purpose.

type ID string

type Identifier interface {
	ID() cchat.ID
}

Entity

An entity describes a base entity that the backend describes. Identities must all be identifiable. Their IDs must be globally unique within a service. Each entity may have its own name, like so:

type Nameable interface {
	Identifier
	Name(LabelContainer) (func(), error)
}

All entities moved over the wire will be kept inside a registry on both the frontend and the backend. They're only modified when methods inside a container are called, which are ContainerUpdateMethods and ContainerDeleteMethods.

As an example, assuming this entity has an ID server:123123 and comes from the service com.github.diamondburned.cchat-discord, then the RPC could reference the Name method like this:

/cchat/com.github.diamondburned.cchat-discord/server:123123/Name

An example of an entity would be Service or Server.

type Server interface {
	Nameable

	AsLister() Lister       // optional
	AsMessenger() Messenger // optional
}

Service

Service is a special type of entity. It defines the scope for which interfaces must each have a unique ID within.

A service may have a name, but the name must be constant.

type Service interface {
	Identifier
	Name() text.Rich
}

type ipcService struct {
	objects map[cchat.ID]
}

Extension

An extension is an interface that is not identifiable on its own. It is never a standalone entity. Instead, an extension must be within an entity and identified as a children of the server.

Over the wire, if the entity is freed, all its extensions will be as well. As a result, extensions will be stored within the entity inside the registry.

As an example, assuming an extension named "Sender" is part of another extension named "Messenger," which is within an entity with the ID server:123123 that comes from the service com.github.diamondburned.cchat-discord, then the RPC could reference the Send method like this:

/cchat/com.github.diamondburned.cchat-discord/server:123123/Messenger/Sender/Send

An entity can not implement an extension simply by returning a nil value from the asserter method.

Examples of extensions are Messenger and Lister, which sits within a Server.

type Messenger interface {
	JoinServer(MessagesContainer) (func(), error)

	AsSender() Sender // optional
	AsEditor() Editor // optional
}

Struct

A struct describes constant data, or more specifically, data that cannot change by itself and will be replaced when they're updated. Structs may be nested arbitrary, but they may never contain an entity.

There is no defined way to reference a struct over the wire. Methods must accept an ID that the struct includes, and how the ID is accessed is specific to each type of struct. This is intended.

An example of a struct is MessageCreate.

type MessageHeader struct {
	ID     cchat.ID
	Author text.Rich
}

type Message struct {
	MessageHeader
	Content text.Rich
}

Container

A container is a widget that the frontend implements that keeps track of a state. Containers must not contain any methods except for ContainerUpdateMethods or ContainerDeleteMethods. Each container must also be explicitly identifiable.

Over the wire, each container must have a unique integer ID. How this ID is generated is specific to each RPC implementation. Examples of valid IDs include object pointers in memory, free list, sequence, etc.

An example of a container is MessagesContainer, which is used in Messenger.

type MessagesContainer interface {
	CreateMessages([]Message) // ContainerUpdateMethod, array=true
	UpdateMessage(Message)    // ContainerUpdateMethod
	DeleteMessage(Identifier) // ContainerDeleteMethod
}

Methods

Entities or Extensions may implement certain, limited types of methods.

GetterMethod

Getter methods are methods that have no inputs but the interface that implements the method. These methods must not return an entity or extension.

An example of a GetterMethod would be Service's Name method.

type Service interface {
	Name() text.Rich
}

QueryMethod

Query methods are similar to getter method, except with parameters. Whether or not the method can return a constant is undefined. These methods must not do IO, meaning they must be non-blocking. Their results must not be cached. Query methods CAN have side effects; they can be used to implement setters that the frontend call on the backend.

Query methods must NOT take in or return an entity or extension.

An example of a QueryMethod is Editor's IsEditable method.

type Editor interface {
	IsEditable(messageID ID) bool
}

IOMethod

IO methods are methods that are blocking and have proper replies. Because of this, they're synchronous methods. These methods must not take in or return an entity or extension, similar to QueryMethod and GetterMethod.

The returns of these methods are significant, though the meaning depends on each method. For example, returned messages can be assumed that certain tasks have been completed successfully.

An example of an IO method is Send in Sender.

type Sender interface {
	Send(SendableMessage) error
}

SubscribeMethod

Subscribe methods are methods that the backend can implement that would subscribe the given container type to the events specific to that interface. These methods must not do any IO, and they must always return an unsubscribing callback or an error.

An example of a SubscribeMethod is Messenger's JoinServer.

type Messenger interface {
	JoinServer(MessagesContainer) (func(), error)
}

ContainerUpdateMethod

Container update methods are meant to replace either a struct or an entity with a given ID with a new item. These methods are meant to replace items on the frontend and can be useful for updating methods or servers.

Over the wire, the significance of a container update method is that it lets both ends know when to free up an entity. Since structs aren't stored and are constant, they're not relevant in this case. Old entities being replaced, however, will be freed, with the new one set in place. The internal registry will update accordingly.

In order for a wire implementation to determine whether or not a container's update method should touch the registry, it should resolve the item type and determine if it's an entity or a struct. Only if it's an entity should it bother with accessing the ID for memory purposes.

Container update methods are methods that containers must implement. These methods must not do any IO, and they should, if possible, be asynchronous. This means that the returns of these methods should not signify anything.

Semantically, a container update method is defined as such:

type ContainerUpdateMethod struct {
	ItemType string // either an Entity or Struct
	Batch    bool   // true if []ItemType
}

An example of a ContainerUpdateMethod is MessageContainer's UpdateMessage.

type MessageContainer interface {
	CreateMessages([]Message)
	UpdateMessage(MessageUpdate)
}

type MessageUpdate struct {
	Message
	After int
}

ContainerDeleteMethod

Container delete methods are meant to delete a struct or entity from a container. It is mostly similar to ContainerUpdateMethod, except there is no addition or replacement of any new ID, but only deletion.

An example of a ContainerDeleteMethod is MessageContainer's DeleteMessage.

type MessageContainer interface {
	DeleteMessage(ID)
}

JSONRPC

Memory Management

It is assumed that objects are only allocated (meaning saved into the registry) using ContainerUpdateMethods. They're only freed using either ContainerUpdateMethods or ContainerDeleteMethods. This simplifies memory management to only a very small extent.

Method Names

JSONRPC2 defines method names to be used. These method names will resemble paths inside cchat-jsonrpc2.

Below are examples for each method type.

  • Service: /cchat/com.github.diamondburned.cchat-discord
  • Service Method: /cchat/com.github.diamondburned.cchat-discord/Name
  • Entity: /cchat/com.github.diamondburned.cchat-discord/id/server:123123
  • Entity Method: /cchat/com.github.diamondburned.cchat-discord/id/server:123123/Name
  • Extension: /cchat/com.github.diamondburned.cchat-discord/id/server:123123/Messenger/Sender/Send

It is worth noting that, since methods are always capitalized, a lower-case route can only mean an internal keyword or an ID, depending on which route and what position it is in.