Working with locks

Working with locks raevnos Thu, 2004-06-03 14:02

This section is an introduction to dealing with locks: Adding new built-in lock types, evaluating an object's locks, adding or changing individual locks, and so forth.

Overview

The code for locks is located in hdrs/lock.h and src/lock.c. All information about a specific lock is held in a lock_list struct. lock_lists are stored in a linked list sorted by lock name, in a way similar to attributes. The actual key part of a lock, called a boolexp, is handled by hdrs/boolexp.h and src/boolexp.c.

The fields of a lock_list should be accessed via the macros defined in hdrs/lock.h. They are:

L_FLAGS(lock)
The flags of a lock.
L_CREATOR(lock)
The dbref of the player that set the lock.
L_TYPE(lock)
The name of the lock ("Basic", "Enter", etc.)
L_KEY(lock)
The boolexp

Adding new lock types

Adding new lock types raevnos Thu, 2004-06-03 17:06

Adding new lock types that can be set in-game with @lock/foo is very easy.

Near the top of

src/lock.c

is an array named lock_types. Near the end of the array is a comment "Add new lock types just before this line.". Just copy & paste one of the existing entries in the array to that spot, and change the name to that of your new lock type. You can also change the default flags of the lock if you wish (The LF_PRIVATE part).

If you want to test this new lock type in hardcode, you'll also want to add a new line to the section just above the lock_types array, where there's a comment "Define new lock types here.". Once again, use the existing ones as an example.

hdrs/lock.h

also needs to be modified. If you added
'const lock_type Foo_Lock = "Foo"' in

src/lock.c

, then

hdrs/lock.h

needs to have 'extern const lock_type Foo_Lock;". This new variable is used with the eval_lock() function described later. It's not neccessary to use, but it's easier than re-typing the actual name of the lock everyplace you want to test it.

Boolexps

Boolexps raevnos Mon, 2007-06-04 22:53

A boolexp is the key part of a @lock: #1, sex:M*, canpass/1, etc. This page talks about their implementation and describes how to add a new type of key.

Well... a new key of a 'X^Y' sort. Adding new tokens is tricky for a couple of reasons: There just aren't that many possible ASCII characters left, and it's a lot more work than extending ^. (Which really should have been used to mean an exclusive-or to go along with & and |, but too late now. Too bad I didn't think of it back when...).

Implementation

All boolexp-related code is in src/boolexp.c.

Keys are transformed into a parse tree by a hand-written recursive descent parser (The top function is parse_boolexp_E()). This parse tree is then compiled to bytecode. The bytecode itself is usually stored in the chunk manager; the boolexp field of the lock structure holds a chunk reference.

Some locks are checked very frequently, hence the use of bytecode to make evaluating a boolexp as fast as possible.

Adding a new key type

Adding a completely new key type involves extending the parser and "assembler", and the VM function that evaluates the bytecode, and the function that converts bytecode to a string for display, and and and...

Adding a new ^-type key involves several fewer steps (But still quite a few). This is a brief guide; look at the comments and existing code in src/boolexp.c for hints:

  1. Add a new opcode to the bvm_opcode enum.
  2. Add a new entry to the flag_locks array.
  3. Add a case for the new opcode to the switch in eval_boolexp(). The variable r is the result register; set it to 1 if the key succeeded, 0 if it failed. The value of (char*)(bytecode + arg) is the string on the right hand side of the ^.
  4. Add a case for the new opcode to the switch in unparse_boolexp() to convert the bytecode into a string. Use OP_TFLAG as a template.
  5. Add a case for the new opcode to the switch in emit_bytecode(). Just adding it to the same group as OP_TFLAG and letting it fall through is usually all that's needed here.
  6. Add a case for the new opcode to the switch in print_boolexp() to print out a pseudo-assembly instruction. This is used in debugging the boolexp compiler.
  7. Optionally add a case for the new opcode to the switch in check_lock() to carry out @warnings checks.

Future

emit_bytecode() might move from a switch to, on GCC, an optimized jump table. Macros will be used to choose which version to use with only one evaluation function, using the same approach as the ocaml bytecode VM.

  • Testing locks

    Testing locks raevnos Thu, 2004-06-03 17:18

    Testing to see if one object passes another object's lock is, again, easy to do.

    The usual way to test is with the eval_lock() function. It takes three arguments: The dbref of the object that is trying to pass the lock, the dbref of the object whose lock is being testing, and the name of the lock type. This last argument is usually one of the Foo_Lock variables listed in

    hdrs/lock.h

    , or can be a string containing the name of the lock. Enter_Lock and "Enter" will test the same lock, but the former is the recommended usage. eval_lock() returns a true value if the lock is passed, and false otherwise. Remember that if the lock doesn't exist on the object in question, it always succeeds.

    If all you have is a boolexp (The 'key' part of a lock) and not an actual lock on an object (A channel lock, for example), it can be tested with the eval_boolexp() function. Once again, it takes three arguments: The dbref of the object trying to pass the lock, the boolexp, and the dbref of the object to pretend that the boolexp is on, for eval-locks, permission and visibility checks and so forth. If there is no object associated with the boolexp, such as in channel locks, use the same dbref that's trying to pass the lock.