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.