-
Notifications
You must be signed in to change notification settings - Fork 3
/
MODULES
826 lines (626 loc) · 32.7 KB
/
MODULES
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
DYNAMICALLY LOADABLE MODULES
TinyMUSH versions 3.1 and higher provide an interface for dynamically
loadable modules. This allows server administrators to write and install
"hardcode" that extends the functionality of the server, without creating
undue difficulties with future upgrades.
If you are writing a module, or you are installing a module written by
someone else, please be forewarned: Modules muck directly with the
server's internals. Using a module is, for all practical purposes,
exactly like directly hacking the server. Thus, the safeguards that
exist with softcode do not exist with modules; if modules contain
bugs, they could cause severe issues with instability and database
corruption.
If you are writing a module, you should be fluent in reading and
writing the C programming language. You should take the time to
carefully study the existing server code. It's assumed you can
read code to figure out things that aren't explicitly documented
(which is just about everything not directly related to module
interface). It's also assumed that you understand how dynamic
loading works.
-----------------------------------------------------------------------------
HOW TO BUILD A MODULE
Every module must have a one-word name, such as "hello", or
"space3d", referred to generically as '<name>' in the rest of this
document. This is the prefix of the shared object files that are
loaded by netmush.
The source for your module should be placed in src/modules/<name>.
The directory must use the same name as the shared object files your
code produces. The shared objects for modules go in the game/modules
directory.
In your module's source directory you should have at least 5 files:
configure.in, configure, Makefile.inc.in, .depend, and a C source
file. Your best bet to get started is to make a copy of the 'hello'
module source, substitute your own code for hello.c, and modify
Makefile.inc.in appropriately. More advanced module code might make
use of a customized configure script and Makefile, but that is beyond
the scope of this document.
NOTE: You must change the AC_INIT macro at the top of your module's
configure.in to reference one of your C source files (it doesn't
matter which one as long as it exists) and then regenerate the
configure script using the GNU autoconf utility. Please see:
http://www.gnu.org/software/autoconf/autoconf.html
Getting the server to load a module is simple. For each module, add
the following line to your netmush.conf file:
module <name>
Modules can only be loaded when the game is started.
CAUTION: If you are changing a dynamically-loaded module while the
game is running, do NOT overwrite the old shared objects. The Makefile
fragments we've included will move old .so files out of the way,
but because they keep only one backup copy, they won't help if you
rebuild your module twice before reloading it into the game. To be
certain, you must go to game/modules/ and rename the .so file
associated with your module.
-----------------------------------------------------------------------------
MODULE NAMES AND SYMBOLS
Everything within a module should occupy a unique namespace. Functions
names, non-static global variables, and other public entities should
be named 'mod_<name>_<regular name>'. Macros and constants should be named
MOD_<name>_<regular name>. This avoids potential namespace collisions.
All modules should begin with the following line:
#include "../../api.h"
Including this file should get you access to the commonplace structures
and functions in the MUSH server, as well as specific module API things.
Please note that if you plan to use any symbol that's not defined within
api.h or a file that is #include'd by api.h, you will either need to
declare it extern within your module's .c file, or #include the
appropriate main server .h file, as appropriate.
CAUTION: Not all operating system's dlopen() calls properly check for
unresolved references. On such a system, if you reference a symbol that
your module cannot locate because it's not local and it wasn't declared
as extern, the server will crash when it tries to resolve the symbol.
-----------------------------------------------------------------------------
MODULE CONFIGURATION
The module configuration should come next, since the rest of your module
will use this information.
You should declare a configuration structure for your module as follows:
struct mod_<name>_confstorage {
<type1> <name1>;
<typeN> <nameN>;
} mod_<name>_config;
Then, you should declare a configuration table, as follows:
CONF mod_<name>_conftable[] = {
{(char *)"<name>_alias", cf_alias, <set perms>, <read perms>,
(int *)&mod_<name>_<table>_htab, (long)"<description>"},
{(char *)"<boolean option>", cf_bool, <set perms>, <read perms>,
&mod_<name>_config.<bool_param>, (long)"<yes/no assertion>"},
{(char *)"<read-only param>", cf_const, CA_STATIC, <read perms>,
&mod_<name>_config.<const_param>, (long)<as appropriate>},
{(char *)"<integer param>", cf_int, <set perms>, <read perms>,
&mod_<name>_config.<int_param>, <maximum value or 0>},
{(char *)"<dbref param>", cf_dbref, <set perms>, <read perms>,
&mod_<name>_config.<dbref_param>, <default valid if invalid>},
{(char *)"<options flagword>", cf_modify_bits,
&mod_<name>_config.<options_param>, (long)<name table>},
{(char *)"<nametable>_access", cf_ntab_access, <set perms>, CA_DISABLED,
(int *)<table>, (long)access_nametab},
{(char *)"<word option>", cf_option, <set perms>, <read perms>,
&mod_<name>_config.<int_param>, (long)<option nametable>},
{(char *)"<whatever>_flags", cf_set_flags, <set perms>, <read perms>,
(int *)&mod_<name>_config.<flag_param>, 0},
{(char *)"<word>", cf_string, <set perms>, <read perms>,
(int *)&mod_<name>_config.<string_ptr>, <maximum length>},
{ NULL, NULL, 0, 0,
NULL, 0}};
Note that the table must always end with a null entry.
In the conf file, any config parameters for a module must, of course,
be placed after module directive loading that module.
-----------------------------------------------------------------------------
MODULE INITIALIZATION
The module initialization function goes LAST in your module's .c file.
It should read as follows:
void mod_<name>_init()
{
/* Initialize local configuration to default values. */
mod_<name>_config.<bool_param> = <0 or 1>;
mod_<name>_config.<int_param> = <integer>;
mod_<name>_config.<string_param> = XSTRDUP(<string>,
"mod_<name>_init");
/* Load tables. */
register_hashtables(mod_<name>_hashtable, mod_<name>_nhashtable);
register_commands(mod_<name>_cmdtable);
register_prefix_cmds("<prefix chars>");
register_functions(mod_<name>_functable);
/* Any other initialization you need to do. */
}
If you have no hashtables, numeric hashtables, commands, and/or functions,
you should still call these functions, with a parameter of NULL.
Note that there is no main() function in a module.
-----------------------------------------------------------------------------
DEFINING COMMANDS
Commands fall into four categories of syntax:
TYPE SYNTAX PATTERN EXAMPLE
NO_ARG <cmd> @stats
ONE_ARG <cmd> <argument> think <message>
TWO_ARG <cmd> <arg 1> = <arg 2> @parent <object> = <parent>
TWO_ARG_ARGV <cmd> <arg 1> = <arg 2a>,<...> @trig <obj>/<attr> = <p1>,<..>
api.h defines a number of macros used to declare command handlers.
These should be largely self-explanatory. The syntax of a command
handler is:
DO_CMD_<type>(mod_<name>_do_<handler name>)
{
/* code goes here */
}
To write a handler for "@hello", a command with one argument in a module
called "hello", for example, you would write:
DO_CMD_ONE_ARG(mod_hello_do_hello)
{
/* code goes here */
}
Once you have declared all of your handlers, you must place the
handlers in the command table, which takes the following format:
CMDENT mod_<name>_cmdtable[] = {
{(char *)"<command1>", <switches>, <permissions>,
0, CS_<handler type>,
NULL, NULL, NULL, {<handler function>}},
{(char *)"<command2>", <switches>, <permissions>,
0, CS_<handler type>,
NULL, NULL, NULL, {<handler function>}},
{(char *)NULL, NULL, 0,
0, 0,
NULL, NULL, NULL, {NULL}}};
You can put as many commands into this table as you'd like. Make
sure it ends with the "null" entry, though.
<switches> is the name of a switch table, described below.
<permissions> consist of an OR'd ('|') list of permissions, such as
CA_PUBLIC, CA_WIZARD, and CA_GOD.
<handler type> is the same as the <handler function> type. For
example, if you have DO_CMD_NO_ARG(mod_hello_do_heythere), then you
will have CS_CMD_NO_ARG for <handler type>. Your <handler function>
will be mod_hello_do_heythere.
A switch table takes the following form:
NAMETAB mod_<name>_<command>_sw[] = {
{(char *)"switch1", <letters>, <permissions>, <key>},
{(char *)"switch2", <letters>, <permissions>, <key>},
{ NULL, 0, 0, 0}};
A command can take an arbitrary number of switches, so each of these
switch tables can be as large as you like. Make sure the switch table
ends with the "null" entry, though.
<letters> is the number of unique letters of the switch that someone
has to type. For example, if your switch name is "foobar", and
<letters> is 3, then '/foo' is sufficient.
<permissions> consist of an OR'd ('|') list of permissions, such as
CA_PUBLIC, CA_WIZARD, and CA_GOD, just like in the main command table.
<key> is a bit key; handler functions check 'key & <key>' to modify
their behavior based on any switches the user passed. If you want
a command to be able to take multiple switches as the same time, you
should add '|SW_MULTIPLE' to <key>.
Switch tables should be placed BEFORE the command table, in your
module's .c file.
If your module defines any single-character prefix commands, you
should enter them as CS_ONE_ARG|CS_LEADIN commands in your command
table, and also pass the relevant prefix characters in a string
to register_prefix_cmds(). See the "-" command in the included mail
module for example.
-----------------------------------------------------------------------------
DEFINING FUNCTIONS
A function handler takes the following form:
FUNCTION(mod_<name>_fun_<function name>)
{
/* code goes here */
}
See functions.h for what this macro'd function prototype expands to.
See the server's fun*.c files for some examples of how to write functions.
Once you have declared all of your function handlers, you must place
them in the function table, which takes the following format:
FUN mod_<name>_functable[] = {
{"FUNCTION1", <handler>, <args>, <flags>, <permissions>},
{"FUNCTION2", <handler>, <args>, <flags>, <permissions>},
{NULL, NULL, 0, 0, 0}};
You can put as many functions into this table as you'd like. Make
sure it ends with the "null" entry, though.
<handler> is the name of the function handler (the name of the C function
that's handling the function call: mod_<name>_<fun>_<whatever>).
<args> is the number of arguments that the function should take.
If this is variable, use 0.
<flags> are any special function-handling flags, OR'd ('|') together.
Functions taking a variable number of arguments get a FN_VARARGS flag.
Functions whose arguments should be passed unevaluated get a FN_NO_EVAL
flag.
<permissions> consist of an OR'd ('|') list of permissions, such as
CA_PUBLIC, CA_WIZARD, and CA_GOD, just like in the main command table.
-----------------------------------------------------------------------------
DEFINING HASH TABLES
HASHTAB mod_<name>_<htabname1>;
HASHTAB mod_<name>_<htabnameN>;
MODHASHES mod_<name>_hashtable[] = {
{ "htabname1", &mod_<name>_<htabname1>, <size factor>, <minimum>},
{ "htabnameN", &mod_<name>_<htabnameN>, <size factor>, <minimum>},
{ NULL, NULL, 0, 0}};
NHSHTAB mod_<name>_<nhtabname1>;
NHSHTAB mod_<name>_<nhtabnameN>;
MODNHASHES mod_<name>_nhashtable[] = {
{ "nhtabname1", &mod_<name>_<nhtabname1>, <size factor>, <minimum>},
{ "nhtabnameN", &mod_<name>_<nhtabnameN>, <size factor>, <minimum>},
{ NULL, NULL, 0, 0}};
The initial size factor will be multiplied by the hash factor.
The minimum is the absolute smallest size of the hash table, which
must be a power of 2.
-----------------------------------------------------------------------------
EXTENDING THE DATABASE
Though modules have access to the db structure, modules cannot add their
own data types to that db structure.
Thus, if you need to store a piece of data for every object in the
database, your module needs to have its own parallel db structure,
as follows:
typedef struct mod_<name>_dbobj MOD_<name>_OBJ;
struct mod_<name>_dbobj {
<type1> <name1>;
<type2> <nameN>;
};
MOD_<name>_OBJ *mod_<name>_db = NULL;
#define OBJ_INIT_MODULE(x) \
mod_<name>_db[x].<name1> = <value1>; \
mod_<name>_db[x].<nameN> = <valueN>;
void mod_<name>_db_grow(newsize)
int newsize;
{
DB_GROW_MODULE(mod_<name>_db, newsize, newtop, MOD_<name>_OBJ);
}
The above will automatically take care of extending and initializing
your private module database as needed. Initialization of newly-created
objects should be taken care of by creating a handler for create_obj()
(see below).
-----------------------------------------------------------------------------
MODULE API FUNCTIONS
The following are "hooks" into the server. If you place these functions
in your module's .c file, they will automatically be called at the
appropriate time.
void mod_<name>_create_obj(dbref player, dbref obj)
Run when an object is created, after other initialization is done.
Passes the creating player and the object dbref created.
void mod_<name>_destroy_obj(dbref player, dbref obj)
Run when an object is destroyed, before the player is compensated
for its destruction, is notified of its destruction, and the
object is wiped out.
Passes the destroying player and the object dbref being destroyed.
Note that the destroying player may be NOTHING.
void mod_<name>_create_player(dbref creator, dbref player,
int isrobot, int isguest)
Run when a player is created, after create_obj() has been run.
void mod_<name>_destroy_player(dbref player, dbref victim)
Run after the player basics have been cleared out, but before
destroy_obj() has been run.
void mod_<name>_announce_connect(dbref player, const char *reason, int num)
Run when a player connects, before the player looks at his current
location, but after all other connect stuff has been executed.
'reason' is the type of connection - guest, create, connect, or cd.
'num' is the player's resulting number of open connections.
void mod_<name>_announce_disconnect(dbref player, const char *reason, int num)
Run when a player disconnects, after all other disconnect stuff
has been executed, but before the descriptor has been deleted.
'reason' describes the disconnection (see 'help Conn Reasons').
'num' is the player's resulting number of open connections.
void mod_<name>_examine(dbref player, dbref cause, dbref thing,
int control, int key)
Run when an object is examined, after the basic object information
is displayed but before attributes are displayed. 'control' is
1 if the player can examine thing, and 0 if not. 'key' is the
key originally passed to the examine command.
void mod_<name>_make_minimal(void)
Run if making a minimal database is specified.
void mod_<name>_cleanup_startup(void)
Run after the restart database is read, but before @startups
on objects are done.
void mod_<name>_do_second(void)
Runs once a second.
-----------------------------------------------------------------------------
MODULE API FUNCTION: process_command
int mod_<name>_process_command(dbref player, dbref cause, int interactive,
char *command, char *args[], int nargs)
Run when any object tries to execute a command, before any
built-in commands or softcoded commands are checked.
Returns a number: 0 on failure, something greater than 0 on
success, and something less than 0 on failure that should stop
further matching of commands.
This handler tries to run on each module in sequence. If the
handler is not defined for a module, or 0 is returned, it tries
the next module. A return of a number other than 0 will result
in not running this handler on the other modules, this time
around.
The calling server function considers a result greater than 0 to
be a successful command match, and will consider the command
taken care of.
The 'player' and 'cause' parameters are the dbrefs of the objects
taking the action, and which caused the action (the enactor): the
equivalent of %! and %#, respectively.
The 'interactive' parameter is passed as 1 if the command was
entered from the keyboard (i.e., received over the network),
and 0 otherwise.
The 'command' parameter is a pointer to the command entered,
space-compressed if space compression is turned on. This
string SHOULD NOT be destructively modified.
The 'args' parameter consists of the stack (%0 - %9) and the
'nargs' parameter is the number of arguments on the stack.
-----------------------------------------------------------------------------
MODULE API FUNCTION: process_no_match
int mod_<name>_process_no_match(dbref player, dbref cause, int interactive,
char *eval_cmd, char *raw_cmd,
char *args[], int nargs)
Run when any object tries to execute a command, after all built-in
and softcoded commands are checked. This occurs right before a
"Huh?" message is given.
Returns a number: 0 on failure, something greater than 0 on
success, and something less than 0 on failure that should stop
further matching of commands.
This handler tries to run on each module in sequence, most
recently-loaded module first, to least recently-loaded module
last. If the handler is not defined for a module, or 0 is
returned, it tries the next module. A return of a number other
than 0 will result in not running this handler on the other
modules, this time around.
The calling server function considers a non-zero result to
be a successful command match, and will consider the command
taken care of. (Note that this is different from process_command,
which considers only results greater than zero to be successful.)
The 'player' and 'cause' parameters are the dbrefs of the objects
taking the action, and which caused the action (the enactor): the
equivalent of %! and %#, respectively.
The 'interactive' parameter is passed as 1 if the command was
entered from the keyboard (i.e., received over the network),
and 0 otherwise.
The 'eval_cmd' parameter is a pointer to the command, after it
has gone through percent-substitutions and other evaluations.
The 'raw_cmd' parameter is a pointer to the command entered,
space-compressed if space compression is turned on, but not
otherwise evaluated.
The 'args' parameter consists of the stack (%0 - %9) and the
'nargs' parameter is the number of arguments on the stack.
-----------------------------------------------------------------------------
MODULE API FUNCTION: did_it
int mod_<name>_did_it(dbref player, dbref thing, dbref master,
int what, const char *def,
int owhat, const char *odef,
int awhat, int ctrl_flags, char *args[], int nargs,
int msg_key)
Run when did_it() is called, before the server actually does
the notification/others-notification/action.
The handler tries to run this on each module in sequence,
most recently-moduled first, to least recently-loaded module
last. If a module returns 0, the handler goes on to the next
module. If the module returns something other than 0, the
handler does not try any further modules. If the module
returns a positive number, the main body of the server's
own did_it() function is not executed. If the module
returns a negative number, the main body of the server's
own did_it() function is still executed.
player is the object doing something, and thing is its "victim".
player is the object that would normally see the <what> message
(@desc, @move, @succ, etc.); thing is the object that would
normally execute the <awhat> action (@adesc, @amove, @asucc, etc.)
what, owhat, and awhat are attribute numbers. def and odef are
the default strings for what and owhat.
ctrl_flags is a bitmask of VERB_NOW and VERB_NONAME. The VERB_NOW
flag is usually the result of passing the /now switch, and normally
affects the queueing of actions. The VERB_NONAME flag normally
prevents the actor's name from being prepended to the odef message.
args and nargs are the stack and number of items on the stack.
msg_key is a bitmask of MSG_SPEECH, MSG_MOVE, and MSG_PRESENCE,
allowing special processing related to the PRESENCE flag.
This function can be used to react to a large number of server
actions, by switching on <what>. For example, all commands that
move objects eventually call did_it() with <what> of A_MOVE.
If you want to intercept all movement, just check for
'what == A_MOVE' and act accordingly in this function.
-----------------------------------------------------------------------------
STORING DATA IN FLATFILES
This is the first and easiest of three different methods available to
a module for storing module-specific data.
Using this method, the module reads its data from a pure ASCII text
flatfile on startup, via a module API hook, as follows:
void mod_<name>_load_database(FILE *f)
This loads the flatfile specific to the module. It is called
at start time, immediately after the main MUSH database and
any module GDBM data (see below) is loaded. This function is
passed a single input parameter, an open, read-only file pointer
for the module flatfile.
Every time the server checkpoints ("database dump" time), it dumps a
flatfile containing that data, via the following module API hook:
void mod_<name>_dump_database(FILE *f)
This dumps a flat text database specific to the module. It
is called at checkpoint time. This function is passed a single
input parameter, an open, write-only file pointer for the
module flatfile.
The module flatfile is named 'mod_<name>.db' and is stored in the
directory specified by the conf parameter database_home. This database
file should NEVER be opened or closed directly.
Only printable ASCII data should be stored in this file. You, the module
author, are responsible for versioning, the format of this file, etc.
Useful functions for reading and writing flatfiles can be found in
the core server's db_rw.c source.
-----------------------------------------------------------------------------
STORING DATA IN GDBM WITH NO CACHING
This is the second of three different methods available to a module for
storing module-specific data.
This method directly stores data within the MUSH's general GDBM
database. GDBM is a 'random-access' database that allows you to store
and fetch individual pieces of data at a time. This is identical to
how the MUSH stores its structure data and requires your module to be
able to dump or convert a printable ASCII flatfile when database
conversion (backups to and restores from flatfile) is run.
Since the MUSH stores many different types of data in GDBM, it has to have a
way to keep these pieces of data seperate. It does so by allocating each
subsystem a 'dbtype' number. A block of numbers is already reserved for
internal MUSH data, and the rest are available for use by modules. Each
subsystem specifies this number when it stores data. Before you may read or
write to GDBM, your module must obtain a dbtype number with the
register_dbtype function:
unsigned int register_dbtype(char *modname)
Assigns a new dbtype ID number for a certain module name. If a
dbtype ID has already been assigned for that module, it will be
returned.
There are four API hooks provided for reading data needed at startup and
checkpointing to GDBM as well as reading and writing flatfiles:
void mod_<name>_db_read(void)
Load any module-specific data from GDBM at startup. Called during
MUSH startup, and during the backup conversion process before
db_write_flatfile() is called. Use this to load data which must be
present when the MUSH starts.
void mod_<name>_db_write(void)
Write any module-specific data to GDBM. Called during MUSH
checkpoints, at shutdown, and during the restoral conversion process
after db_read_flatfile() is called.
void mod_<name>_db_read_flatfile(FILE *f)
Load and convert flat text database specific to module, called
during the restoral db conversion process (the 'Restore' script).
This is identical to the 'load_database' hook except it is ONLY
called during database conversion.
void mod_<name>_db_write_flatfile(FILE *f)
Dump flat text database specific to module, called during the backup
db conversion process (the 'Backup' script). This is identical to
the 'dump_database' hook except it is ONLY called during database
conversion and flatfile generation (IE, @dump/flatfile).
It is important to note that register_dbtype() must be called at the
beginning of each of these four functions since module initialization DOES
NOT TAKE PLACE when your module is loaded during database conversion, and
your module cannot know which of these functions will be called first. If
your module does not use db_read or db_write, you should call
register_dbtype during your cleanup_startup hook to make sure it gets called
before anything else. You cannot call register_dbtype in your init hook
because init is called before the database is opened.
A typical MUSH server run will look like this:
* MUSH startup
* Module initialization: mod_<name>_init()
* Module executes db_read: mod_<name>_db_read()
register_dbtype()
* Module reads data from GDBM
* MUSH runs...
* MUSH and module dump/checkpoint: mod_<name>_db_write()
* MUSH runs...
* MUSH and module dump/checkpoint: mod_<name>_db_write()
* MUSH runs...
* MUSH shutdown: mod_<name>_db_write()
A backup database process will look like this:
* Database conversion begins
* Module executes db_read: mod_<name>_db_read()
register_dbtype()
* Module reads data from GDBM
* Module writes flatfile: mod_<name>_db_write_flatfile()
* Database conversion ends
A restoral process will look like this:
* Database conversion begins
* Module reads flatfile: mod_<name>_db_read_flatfile()
* Module executes db_write: mod_<name>_db_write()
register_dbtype()
* Module writes data to GDBM
* Database conversion ends
Reading and writing to GDBM only during db_read() and db_write() doesn't
offer much advantage over just using flatfiles. However, the advantage of
GDBM is that you may read or write any piece of data at any time instead of
having to do it all at once. So while the MUSH is running, if your module's
data is modified you may choose to write just that data immediately or wait
until db_write() to write it. In any case, you must make sure that all of
your module's data is stored when db_write() is finished to ensure
consistency when the MUSH is shut down.
Note that during the database conversion process you may have your module
read a piece of data from GDBM and write it immediately to a flatfile, or
vice versa.
So far we've told you when you can read and write, but not how. Another
advantage of GDBM is that you are not limited to printable ASCII data, but
that you can store binary data of arbitrary length.
DBData is a structure used by the database API which contains two
items: dptr (a pointer to a piece of data) and dsize (the length of
that data). DBData is used by the API interface both for search keys
and returned or stored data. DBData is declared thusly:
typedef struct {
void *dptr;
int dsize;
} DBData;
There are three functions available for reading, writing, and deleting data
in the database:
DBData db_get(DBData key, unsigned int dbtype)
Retrieves data from GDBM.
'key' is a DBData structure that contains a search key of
binary data as well as the length of that data.
'dbtype' should be the dbtype ID registered for your module by
register_dbtype().
db_get returns a DBData structure which contains the returned data
(or NULL if the key cannot be found in the database) as well as the
length of that data. It is *your* responsibility to free() the data
returned by db_get.
int db_put(DBData key, DBData data, unsigned int dbtype)
Stores or replaces data in GDBM.
'key' is a DBData structure that contains a search key of
binary data as well as the length of that data.
'data' is a DBData structure which contains the binary data to be
stored and the length of that data.
'dbtype' should be the dbtype ID registered for your module by
register_dbtype().
db_put returns 0 on success and 1 on failure.
int db_del(DBData key, unsigned int dbtype)
Deletes data from GDBM.
'key' is a DBData structure that contains a search key of
binary data as well as the length of that data.
'dbtype' should be the dbtype ID registered for your module by
register_dbtype().
db_del returns 0 on success and 1 on failure.
Your search key may be a string, a number, or binary data of any length.
For example, if you want your search key to be three integers, you will
create a buffer of length 'sizeof(int)*3' and copy your three integers into
it. If you want it to be a null-terminated string, make sure your key length
is 'strlen(string) + 1'.
-----------------------------------------------------------------------------
STORING DATA IN GDBM WITH CACHING
This is the third of three different methods available to a module for
storing module-specific data. It is fundamentally similar to the second
method, except data is cached in memory.
In MUSH, the cache is essentially a front-end to GDBM, and interacting with
it is identical. In fact, the function calls match almost exactly. Instead
of db_get(), db_put(), and db_del(), you use cache_get(), cache_put(), and
cache_del(). The parameters to the cache functions are the same ones you
pass to their GDBM counterparts. The cache acts as a 'buffer', making sure
the most often accessed data is already in memory, and making writes more
efficient by buffering them until a number of entries may be written at
once. You don't need to worry about how the cache operates; it will make
sure everything gets read and written properly.
Note that you cannot pass a non-malloc()ed piece of memory to cache_put. It
must have been malloc()ed, and it will become the cache subsystem's
responsibility to free it.
-----------------------------------------------------------------------------
WHICH METHOD OF STORING DATA TO USE
If the amount of data your module works with is small and fits easily into
memory, use simple flatfiles with load_database() and dump_database() hooks.
You do not need to worry about database conversion since all of your data is
always in a flatfile!
If you need random access to your data, consider using GDBM. The main server
uses GDBM to store everything, from object structures to attribute names.
Since it can write individual entries, it only has to write entries that
have changed instead of writing them all, thus making the process more
efficient. It also allows you to easily store binary data.
If your module with large pieces of data that are needed infrequently,
considering using the cache. MUSH attributes are stored using the
cache, and data like mail messages or large pieces of text are perfect
candidates for it.
As an example, the main server does not cache object structures or
attribute names. It reads these completely in at startup in its own
db_read(), and writes all changes out during every db_write(). It does,
however, cache attribute text, and may read or write attributes using
the cache at any time while the MUSH process is running. A similar
process takes place during database conversion. When it is creating a
flatfile from GDBM, MUSH will first load all object structures and
attribute names into memory during db_read(). When db_write_flatfile()
executes, it will write a printable ASCII text representation of
object structures and attribute names to the flatfile, and it will
fetch attributes from GDBM and write them to the flatfile one at a
time. At no time does it have all attributes in memory, and it does
not even use the cache during database conversion, instead using the
direct to GDBM routines.
IMPORTANT NOTE: If you use GDBM, it is your responsibility to clean up your
entries if they are no longer used. Since GDBM is not a relational database
(you cannot do a wildcard search, you *must* search for an exact key), you
must keep track of every entry that you write to it. Look at the code in
db_rw.c that writes attribute names for an example of how to do this. If
your module writes entries to GDBM and never deletes them, they will clog
the database until it is converted to flatfile and back, dropping the unused
entries in the process.
-----------------------------------------------------------------------------
GETTING NOTIFIED WHEN DATA CHANGES
You can have your module notified when a key is stored or deleted in the
cache using the following API hooks:
int cache_put_notify(DBData key, unsigned int dbtype)
This hook is called when a piece of data is added to or changed in
the MUSH cache. Your module should check for an applicable data type
before using the key.
int cache_put_notify(DBData key, unsigned int dbtype)
This hook is called when a piece of data is deleted from the MUSH
cache. Your module should check for an applicable data type before
using the key.