@feed
@feed/<switch> <kind>[/<key>][=<value>]
A feed is a stream of messages that a game’s own systems are built on: a radio, a phone’s text messages, a staff log. The engine gives every message an id, keeps it within the feed’s limits, hands it to the system to deliver, and passes it on to anything else that listens. Players never use @feed. They use the system’s own commands (+radio, +text), and the system’s code runs @feed for them.
A feed belongs to a kind, such as radio, and is named <kind>/<key>: radio/101.5. Defining a kind, and its settings, locks and taps, needs the feed.admin permission, which the wizard role allows. Everything about one feed needs control of the kind’s owner object, or feed.admin.
- @feed kinds: defining a kind, and its owner
- @feed listing: listing kinds and feeds
- @feed members: who a feed’s lines go to, gags and read marks
- @feed rename: moving a feed to a new key
- @feed sending: sending a line, and its style
- @feed delivery: what happens to a line
- @feed tie-ins: the ROUTE, DELIVER and FORMAT attributes
- @feed tie-in arguments: what each tie-in and tap receives
- @feed taps: other systems that hear every line of a kind
- @feed settings: limits on lines, and purging them
- @feed locks: who may be joined and who may speak
- @feed example: a working radio
The read functions, such as feedsof(), are in feed functions.
@feed/define <kind>=<owner>@feed/undefine <kind>@feed/describe <kind>=<description>
A kind is a word in lower case of up to 32 letters, digits, ., - and _, starting with a letter. A key is up to 200 characters with no / and no spaces. A feed starts when it gets its first member or line; there is no command to create one.
@feed/define radio=Radio makes the kind radio, owned by the object Radio. The owner’s attributes are the kind’s tie-ins (see @feed tie-ins), and code that controls the owner runs its feeds. Defining a kind that exists gives it a new owner. The name channel is taken: channel recall is kept as feed lines of the engine’s own channel kind, which @storage counts but @feed and the feed functions cannot reach.
When the owner object is destroyed, its kinds go to the player who owned it, who could already run them. A destroyed player’s kinds go to the probate judge (@config probate_judge), as their channels do. The kinds keep their feeds and lines. A destroyed object also leaves every feed it was on, and its taps are removed. See @destroy.
@feed/describe sets the description @feed/list shows. @feed/undefine removes a kind with all of its feeds, their members and lines, and its taps.
Listing kinds and feeds
Section titled “Listing kinds and feeds”@feed[/list]@feed[/info] <kind>[/<key>]
@feed/list lists the kinds you run, with their owners, how many feeds, lines and taps each has, what its lines take on disk, and their descriptions. @feed/info <kind> shows a kind’s settings, locks, taps and the same totals; @feed/info <kind>/<key> shows one feed’s lines, members, settings and locks.
A feed’s lines are counted three ways: how many, the size of their text (what max_bytes limits), and what they take stored, which includes the names and keys kept with each line. The same totals are the sharpmush_feed_lines and sharpmush_feed_stored_bytes gauges, one per kind.
Members
Section titled “Members”@feed/join <kind>/<key>=<object>@feed/leave <kind>/<key>=<object>@feed/gag <kind>/<key>=<object>and@feed/ungag@feed/seen <kind>/<key>=<object>[/<id>]@feed/who <kind>/<key>@feed/delete <kind>/<key>
A feed’s members are who its lines go to. The engine keeps them so each system doesn’t keep its own list. Only system code changes them, with these commands; a destroyed object stops being a member.
/joinadds a member. The object must pass the kind’s and the feed’s read locks. A member starts with nothing unread./leaveremoves one./gagkeeps a member who receives nothing until/ungag./seenmoves a member’s last-seen mark to a line id, or to the newest line. feedunread() counts the lines after it.
@feed/who <kind>/<key> lists the members, their marks, the newest line id when they joined, and how far they have read. @feed/delete <kind>/<key> removes the feed with its members and lines.
Changing a feed’s key
Section titled “Changing a feed’s key”@feed/rename[/override] <kind>/<key>=<new key>
A feed’s lines and members are stored under its key. Sending to a new key starts a new, empty feed, and the old key keeps its lines (still counted by @storage and @feed/list) and its members until it is moved or deleted.
@feed/rename radio/101.5=102.1 moves a feed to a new key of the same kind. Every line keeps its id, so ids already handed out still work with feedmsg(), and each member keeps how far they have read. If a feed is already at the new key, @feed/rename refuses, and @feed/rename/override merges the two: the lines go together in id order, a member of both keeps their place on the new key, and the new key keeps its own settings and locks.
A key is best something that never changes, such as a number, with the name players see kept in the system’s own attribute. Renaming then changes only that attribute, and nothing in the feed moves. Code that saved the old key (a list of feeds, a ROUTE attribute, a tap that files lines by key) must be changed by the system.
Sending a line
Section titled “Sending a line”@feed/send[/<style>] <kind>/<key>=<message>@feed/send/to <kind>/<key>=<objids>/<message>@feed/send/as <kind>/<key>=<name>/<message>
@feed/send radio/101.5=Coming in. sends a line. It is said by the enactor (%#), the player the system speaks for, so a global +radio command sends as the player who typed it. The enactor must pass the kind’s and the feed’s send locks, unless they may run the kind themselves.
A line has a style: say, pose, semipose, emit or announce. A leading : poses and ; semiposes; /say, /pose, /semipose, /emit and /announce choose one outright and keep the message as it is. Otherwise the line takes the feed’s style setting, say unless set.
/to passes a list of objids before the message, separated from it by /, for the kind’s ROUTE attribute to use: @feed/send/to text/ann-bo=#5:1700000000 #9:1700000123/On my way.
/as gives the line a name to appear under, such as a persona or a callsign, separated from the message by /: @feed/send/as radio/101.5=Ghost/Anyone out there?. It is stored with the line, so recall shows it as it was; the default line uses it in place of the speaker’s name. With both, the objids come first: =<objids>/<name>/<message>.
What happens to a line
Section titled “What happens to a line”- It is refused if it is longer than the feed’s
max_length. - It is stored, unless the feed is not
logged, dropping the feed’s oldest lines past its limits. - It goes to the members who are not gagged, or to whoever ROUTE returns instead.
- It is delivered by DELIVER, or else to each recipient who can hear the speaker, as FORMAT’s line or the default line.
- It is passed to every tap, queued.
The default line is <radio/101.5> Ann says, "Coming in.", or <radio/101.5> Ann keys the mic. for a pose and <radio/101.5> Static crackles. for an emit or announcement. See @feed tie-ins for ROUTE, DELIVER and FORMAT.
Tie-in attributes
Section titled “Tie-in attributes”These attributes on the kind’s owner decide where a line goes and how it reads. Each is named after the kind, and each is optional. They are evaluated as the owner, with the speaker as enactor (%#). Each line has been stored by then, so feedmsg() can read it from %<id>.
An unrestricted Discord channel may supply an external speaker with no game object. Its speaker and enactor are #-1; its stored name and display name identify the Discord member. Formats and taps should use %<name> or feedmsg(%<id>,name) rather than calling name(%<speaker>). External members cannot pass a configured kind or feed send lock. Normal membership gag settings still apply; the default delivery also skips recipients with a configured Interact lock, since an external identity cannot satisfy an object-based lock. A custom DELIVER remains responsible for its own delivery rules, as for ordinary feed lines. Use help @discord channels while the Discord plugin is installed for its channel controls.
FEED<KIND>ROUTEreturns the objids that should receive the line, separated by spaces. Without it, the line goes to the members who are not gagged.FEED<KIND>DELIVERis run as an action list, in place, once per line, and does the delivery itself (@pemit,@message, anything).FEED<KIND>FORMAT, when there is no DELIVER, is evaluated for each recipient who can hear the speaker, and is the line that recipient sees; an empty result sends them nothing.
Tie-in arguments
Section titled “Tie-in arguments”Each tie-in reads what it needs by name (see named arguments). A name means the same thing in every tie-in and tap:
| Name | Value | ROUTE | DELIVER | FORMAT | tap |
|---|---|---|---|---|---|
%<id> | the line id | %0 | %0 | %6 | %0 |
%<key> | the feed key | %1 | %1 | %4 | |
%<feed> | the feed, <kind>/<key> | %1 | |||
%<speaker> | the speaker | %2 | %3 | %2 | %3 |
%<style> | the style | %3 | %4 | %3 | %4 |
%<text> | the message | %4 | %5 | %0 | %5 |
%<name> | the /as name | %7 | %6 | %5 | %6 |
%<audience> | the members who are not gagged | %5 | |||
%<to> | the /to list | %6 | |||
%<recipients> | who receives it | %2 | %2 | ||
%<recipient> | the one recipient this line is for | %1 | |||
%<origin> | an outside line’s source, discord/<message id> | %7 | |||
%<picture> | an outside speaker’s picture, an image URL | %8 |
Both are empty for a line sent in the game. The positions are there for code written before the names; new code is easier to read with the names.
@feed/tap <kind>=<object>/<attribute>@feed/untap <kind>=<object>/<attribute>
A tap is another system that hears every line of a kind, such as a scene recorder or a staff log. @feed/tap radio=Logger/LOGRADIOadds one;*in place of the kind hears every kind. You must control the tap's object.@feed/untap` removes one.
For each line, after it is delivered, the tap’s attribute is queued on its object with the speaker as enactor. It gets the names in the table under @feed tie-in arguments. A tap whose object or attribute is gone is skipped.
Settings
Section titled “Settings”@feed/set <kind>[/<key>]/<option>=[<value>]@feed/purge <kind>/<key>[=<age>]
@feed/set <kind>/<option>=<value> sets an option for every feed of a kind (with feed.admin); @feed/set <kind>/<key>/<option>=<value> sets it for one feed, over the kind’s. An empty value unsets it, so a feed goes back to its kind’s value and a kind to the default.
| Option | Default | Takes |
|---|---|---|
max_messages | 500 | lines kept; the oldest go first; 0 keeps every line |
max_bytes | none | total size of the lines kept; 0 for none |
max_length | none | the longest message, in characters; a longer one is refused, not cut; 0 for none |
max_age | none | lines older than this go when the feed is next written to, and in an hourly pass for feeds nobody writes to: 30d, 12h, 90m; 0 for none |
logged | yes | no keeps no lines; they are still delivered and tapped |
style | say | the style a line has when the sender gives none |
@feed/purge <kind>/<key> drops every stored line of a feed, and @feed/purge <kind>/<key>=30d the lines older than 30 days.
> @feed/set radio/max_messages=200> @feed/set radio/911/logged=no@feed/lock <kind>[/<key>]/<read or send>=<lock>@feed/unlock <kind>[/<key>]/<read or send>
A feed has two locks, ordinary locks evaluated as if they were on the kind’s owner (see lock keys; role^ and perm^ keys work, see roles):
readdecides who may be joined, checked against the object being joined.senddecides who may speak, checked against the speaker.
@feed/lock sets one, and @feed/unlock clears it. A lock on a kind needs feed.admin; a lock on one feed needs only control of the owner, and applies on top of the kind’s lock. A lock that is not set passes everyone. Any other rule about who may do what, such as who moderates a radio frequency, belongs to the system: it keeps its own locks and checks them with testlock() before it runs @feed.
An evaluation lock (<attribute>/<value>) gets %0 the feed key and %1 the kind, so one attribute on the owner can answer for every feed: with @feed/lock radio/send=LKMEMBER/1, LKMEMBER can look %0 up in the radio’s own member lists.
> @feed/lock radio/911/read=role^police> @feed/lock radio/send=role^approvedExample: a radio
Section titled “Example: a radio”The Radio object sits in the master room, so its commands are global. It needs no ROUTE or DELIVER: the members are the audience, and FORMAT is the line.
> @feed/define radio=Radio> &FEED`RADIO`FORMAT Radio=<Radio %<key>> [name(%<speaker>)]: %<text>> &CMD`TUNE Radio=$+tune *:@dolist/inline feedsof(%#,radio)=@feed/leave ##=%#;@feed/join radio/%0=%#;@pemit %#=Tuned to %0.> &CMD`RADIO Radio=$+radio *:@assert setr(f,first(feedsof(%#,radio)))=@pemit %#=Tune in first.;@feed/send %q<f>=%0> &CMD`OFF Radio=$+radio/off:@dolist/inline feedsof(%#,radio)=@feed/leave ##=%#;@pemit %#=Radio off.> +tune 101.5Tuned to 101.5.> +radio Coming in.<Radio 101.5> Ann: Coming in.feedsof() and the other read functions are in feed functions.
::: seealso