TinyMUX

Channel System Internals

Hardcode

The channel communication system (comsys) provides a persistent, named-channel messaging layer in TinyMUX. Players subscribe to channels with short aliases, then use those aliases to speak, pose, and listen. The system is implemented in comsys.cpp and its header comsys.h.

Core Data Structures

Three C++ structures underpin the comsys:

  • struct channel – Represents a single channel. Holds the channel name (up to 50 characters), a display header (up to 100 characters), type flags, charge settings, a channel object dbref, a user array, a linked list of currently-connected users (on_users), and a running message count.

  • struct comuser – Per-channel user record. Tracks the player’s dbref, whether the user is currently on the channel (bUserIsOn), their comtitle text, whether comtitles are displayed (ComTitleStatus), and whether join/leave messages are gagged (bGagJoinLeave).

  • comsys_t – Per-player alias table. Maps each player to their set of channel aliases. Aliases are stored in a flat array of fixed-size slots (ALIAS_SIZE = 16 bytes each), kept in sorted order for binary search lookup. A player may hold up to 100 aliases.

The global comsys_table is a hash table of 500 buckets, keyed by dbref % NUM_COMSYS, with chained overflow. This provides O(1) average lookup for a player’s alias table.

Channel Creation and Configuration

Only Wizards can create channels. @ccreate <name> allocates a new struct channel with default flags (player join, transmit, and receive enabled). After creation, the channel owner uses @cset to configure behavior:

SwitchEffect
@cset/publicChannel appears in everyone’s @clist.
@cset/privateChannel is hidden from @clist.
@cset/loudAnnounces player connects and disconnects.
@cset/muteSuppresses connect/disconnect announcements.
@cset/spoof (or /anon)Comtitle replaces the player name entirely.
@cset/header <channel>=<text>Sets a custom prefix for channel output.
@cset/object <channel>=<dbref>Associates a channel object for locks and descriptions.
@cset/log <channel>=<n>Enables message logging with a rolling buffer of up to 200 entries.

@ccharge <channel>=<amount> sets a per-message coin cost; proceeds go to the channel owner. @cchown transfers ownership.

Channel Flags and Access Control

Channel behavior is governed by a bitfield in channel.type. The defined flags are:

  • CHANNEL_PLAYER_JOIN, CHANNEL_PLAYER_TRANSMIT, CHANNEL_PLAYER_RECEIVE – Player-level access bits, controlled by @cpflags.
  • CHANNEL_OBJECT_JOIN, CHANNEL_OBJECT_TRANSMIT, CHANNEL_OBJECT_RECEIVE – Object-level access bits, controlled by @coflags.
  • CHANNEL_LOUD – Connect/disconnect announcements.
  • CHANNEL_PUBLIC – Visible in @clist to all.
  • CHANNEL_SPOOF – Anonymous/spoof mode.

Flags always override locks. When a flag is cleared (@cpflags <channel>=!join), the corresponding lock on the channel object takes effect. The join lock is the standard @lock, the transmit lock is the @lock/use, and the receive lock is the @lock/enter on the channel object.

Channel Objects

A channel object is a regular game object associated with a channel via @cset/object. It provides:

  • Description – The @desc of the channel object serves as the channel description shown in @clist.
  • Locks – Join, transmit, and receive access control (see above).
  • Custom messages@comjoin, @comleave, @comon, and @comoff attributes are evaluated and shown to the acting player.
  • Speech overrides@saystring and @speechmod on the channel object override player settings for channel output.
  • Message logging – The MAX_LOG attribute sets the rolling log size. Messages are stored in HISTORY_N attributes on the channel object itself, using modular indexing. The LOG_TIMESTAMPS attribute, when set, prepends a local timestamp to each logged entry.

Message Flow

When a player types <alias> Hello!, the command parser calls do_comsystem, which resolves the alias to a channel name via binary search in the player’s comsys_t. This invokes do_processcom, which handles the sub-commands on, off, who, and last, or falls through to message transmission.

For a normal message, the flow is:

  1. Access checktest_transmit_access verifies the player can speak on the channel.
  2. Charge – If the channel has a per-message cost, the player is charged and the owner is credited.
  3. BuildBuildChannelMessage constructs two message strings: one with the comtitle prepended (messNormal) and one without (messNoComtitle). In spoof mode, only the comtitle version is built. The channel header, comtitle, player moniker, and the say/pose/semipose text are assembled. If a channel object has @saystring or @speechmod, those override the player’s own settings.
  4. DeliverSendChannelMessage iterates the on_users linked list. For each user who is active and passes test_receive_access, the appropriate message variant is sent via notify_with_cause_ooc with MSG_SRC_COMSYS. Users who have gagged join/leave messages skip those notifications. The channel’s num_messages counter is incremented.
  5. Log – If the channel object exists and has MAX_LOG set, the message is written to a HISTORY_N attribute on the channel object using modular arithmetic on the message count.

Messages are truncated to 3500 bytes before processing.

Player-Side Commands

CommandPurpose
addcom <alias>=<channel>Subscribe to a channel with the given alias.
delcom <alias>Remove a channel subscription.
comlistShow your current aliases and channels.
allcom <on|off|who>Operate on all subscribed channels at once.
clearcomRemove all channel subscriptions.
comtitle <alias>=<title>Set a per-channel display title.
comtitle/gag <alias>Suppress join/leave messages on a channel.
<alias> <message>Speak on the channel.
<alias> :<pose>Pose on the channel.
<alias> on|off|who|last [N]Join, leave, list members, or recall history.

All channel names and aliases are case-sensitive. Aliases may be 1 to 15 printable ASCII characters with no spaces.

Persistence

Channel data is persisted through two mechanisms:

  • Flatfile (comsys.db) – The save_comsys function writes a versioned flatfile (currently +V5) containing all channel definitions and per-player alias mappings. The file is written atomically: data goes to a temporary .# file, then is renamed over the original. This file is used during dbconvert import/export and as a fallback loader.

  • SQLite write-through – Every mutation (channel create/delete, user add/remove, alias change, title change, message count increment) is immediately written through to the SQLite database via helper functions such as sqlite_wt_channel, sqlite_wt_channel_user, sqlite_wt_player_channel, and their delete counterparts. Write-through is suppressed during initial load (mudstate.bSQLiteLoading). In normal operation, SQLite is the authoritative store.

Performance and Scaling

The comsys is designed for typical MUD workloads of tens to hundreds of channels with thousands of players:

  • Alias lookup is a binary search over the player’s sorted alias array, giving O(log n) per command parse.
  • Player-to-comsys lookup uses a 500-bucket hash table, giving O(1) average access.
  • Message delivery is O(m) where m is the number of active users on the channel, iterating the on_users linked list. The UNDEAD macro filters out disconnected players and garbage objects.
  • Channel user capacity is set at 1,000,000 per channel (MAX_USERS_PER_CHANNEL), though practical limits are far lower.
  • Periodic cleanup via purge_comsystem removes entries for players with zero channels or destroyed objects, preventing unbounded growth of the comsys table.
  • Message recall is capped at 200 entries (MAX_RECALL_REQUEST), stored as attributes on the channel object with modular indexing, so log space is bounded.