Author Topic: heart_beat v. call_out  (Read 5325 times)

Offline Stavros

  • Acquaintance
  • *
  • Posts: 36
    • View Profile
heart_beat v. call_out
« on: April 22, 2010, 12:14:51 PM »
OK, my question is, should I default to using heart_beat, or call_out? I've tried to do some research:

http://dead-souls.net/ds-creator-faq.html#2.36
The Dead Souls Creator FAQ says always use heart_beat.

http://dead-souls.net/docs/lpc/intermediate/chapter2
The omni-present LPC docs say it depends on the application. (see 2.4)

The "Performance" file in the root directory of FluffOS's source (couldn't find online copy) says not to give objects heartbeats unless really necessary, and to do as little in the heart_beat function as possible.

http://lpmuds.net/forum/index.php?topic=743.msg4308#msg4308
I was digging a little deeper, and I found mention of "hearbeat changes" that "should be spelled out gigantic letters" but aren't... (last line of 2nd paragraph of linked post)

I couldn't find very much more on the subject of heart_beat changes with the standard google searches. The Changelogs of FluffOS weren't very informative either, at least to someone with my relatively limited knowledge of the driver.

So, heart_beat or call_out? And what are the heartbeat changes that everyone should know about? Or is there a doc out there that already covers all of this, but happens to be sitting right in one of blind spots?

Offline detah

  • BFF
  • ***
  • Posts: 190
  • Ruler of 2D
    • View Profile
Re: heart_beat v. call_out
« Reply #1 on: April 22, 2010, 02:44:51 PM »
I think the answer is, it depends.

a) The obvious. If the object does not require any periodic checks of any kind, like an ordinary torch, then do not use callouts or heartbeats.
b) If the object calls a function/event rarely, eg. the first completion of a quest, then use call_outs.
c) If the object needs to make frequent and/or periodic checks on something, like a guild object checking on a guild status, then use heartbeats.
d) If there are contingent breaks in the check, like when a monster in your Enemies list leaves the room, then use heartbeats. ie. for combat, use heartbeats.

But for most instances when you do need to choose, use heartbeats. Most modern processors can handle thousands of heartbeats with no problems.

1.5 cents,
-Detah

Offline chaos

  • BFF
  • ***
  • Posts: 291
  • Job, school, social life, sleep. Pick 2.5.
    • View Profile
    • Lost Souls
Re: heart_beat v. call_out
« Reply #2 on: April 22, 2010, 03:06:22 PM »
One thing I've found is that call_out as a mechanism is really very poorly suited to cyclic, rather than one-time, usage, and because of the way it's structured, the more heavily you use it for cyclic calls, the more inefficient it gets.  I made a centralized heart_beat based mechanism for scheduling recurring interval-based calls to unique closures on a best-effort basis, and it vastly improved performance.  I'll prolly put it up on code vault sometime and go into more detail.

Offline hamlet

  • Acquaintance
  • *
  • Posts: 46
    • View Profile
Re: heart_beat v. call_out
« Reply #3 on: April 22, 2010, 03:58:00 PM »
And remember that interrupts are better than polling.

Offline Dworkin

  • Acquaintance
  • *
  • Posts: 29
    • View Profile
Re: heart_beat v. call_out
« Reply #4 on: April 22, 2010, 09:06:57 PM »
One thing I've found is that call_out as a mechanism is really very poorly suited to cyclic, rather than one-time, usage, and because of the way it's structured, the more heavily you use it for cyclic calls, the more inefficient it gets.

That is however an implementation matter. There is nothing inherently costly about callouts as such. In DGD, there are no heartbeats, and all the operations needed to simulate them are O(1).

Offline tigwyk

  • Acquaintance
  • *
  • Posts: 45
    • View Profile
Re: heart_beat v. call_out
« Reply #5 on: April 23, 2010, 12:07:02 PM »
And remember that interrupts are better than polling.


One thing I've always wished I was able to use in my Mud programming is interrupts. I got to play with them in other languages, but have yet to discover a way to really implement them efficiently in any of the muds I've worked on.

Perhaps I'm thinking of a more specific example of interrupts versus what I have to work with on these muds, so if someone wants to clarify how they might already exist, that'd be awesome.

Stavros' question has been raised because on the driver/mudlib we're working with, call_outs are costly, and unfortunately pretty unreliable. In fact, due to call_outs randomly disappearing, we've had to create really lame hacks to compensate for that. We're still unable to upgrade the driver to Fluff due to a lack of access and consent from the owner of the box.

Driver is MudOS v21.7, Lib is a heavily, HEAVILY modified NM3.

Offline Raudhrskal

  • BFF
  • ***
  • Posts: 214
  • The MUD community needs YOUR help!
    • View Profile
Re: heart_beat v. call_out
« Reply #6 on: April 23, 2010, 12:57:15 PM »
One thing worth noting is that you never EVER should call set_heart_beat() in DS in players and livings.

Health/mana regen and such use heart_beat, and you would change the speed of these processes if you'd mess with set_heart_beat. If you really need timed stuff in player that is not tied to an item he carries, use provided hooks... or add a new one.
I think, therefore i may be wrong.
Please note that if you met a Raudhrskal in a place that's not related to muds, it wasn't me. *sigh*... back when I started there was zero hits on google for that name...

Offline hamlet

  • Acquaintance
  • *
  • Posts: 46
    • View Profile
Re: heart_beat v. call_out
« Reply #7 on: April 23, 2010, 02:38:24 PM »
Well, I was thinking of something specific that I was messing with the other day when I said "interrupts".  It is an interrupt of sorts....

Some things could use knowing whether a certain file has changed lately.  A dumb way would be to setup polling using call_out() for all things that fit in that category.

Instead, I put a hook in valid_write() that calls a handler with "file xxx is being changed".   the handler keeps track of who might want to know about specific files.

Soooo... interrupts!  You register, and you get told.  No need to poll.  And that's purely lib-level except the driver call to valid_write(), so there's nothing really arcane about it.  I guess it's not precisely what was originally meant by interrupts, but it's clearly the same idea, and clearly better than a bunch of polling.  You know, unless I implement it in a really dumb way.

Offline chaos

  • BFF
  • ***
  • Posts: 291
  • Job, school, social life, sleep. Pick 2.5.
    • View Profile
    • Lost Souls
Re: heart_beat v. call_out
« Reply #8 on: April 23, 2010, 02:58:56 PM »
What you're describing isn't what I would call "interrupts", but "hooks" -- basically aspect-oriented programming mechanisms that allow checks and callbacks to be dynamically added.  I have a whole architecture for those and it's invaluable.  I'd be shocked if major public mudlibs didn't have something equivalent.

Offline wodan

  • BFF
  • ***
  • Posts: 434
  • Drink and code, you know you want to!
    • View Profile
Re: heart_beat v. call_out
« Reply #9 on: April 23, 2010, 04:32:48 PM »
Well, I was thinking of something specific that I was messing with the other day when I said "interrupts".  It is an interrupt of sorts....

Some things could use knowing whether a certain file has changed lately.  A dumb way would be to setup polling using call_out() for all things that fit in that category.

Instead, I put a hook in valid_write() that calls a handler with "file xxx is being changed".   the handler keeps track of who might want to know about specific files.

Soooo... interrupts!  You register, and you get told.  No need to poll.  And that's purely lib-level except the driver call to valid_write(), so there's nothing really arcane about it.  I guess it's not precisely what was originally meant by interrupts, but it's clearly the same idea, and clearly better than a bunch of polling.  You know, unless I implement it in a really dumb way.


interesting, i'm thinking of adding inotify support :)

Offline chaos

  • BFF
  • ***
  • Posts: 291
  • Job, school, social life, sleep. Pick 2.5.
    • View Profile
    • Lost Souls
Re: heart_beat v. call_out
« Reply #10 on: April 23, 2010, 04:45:57 PM »
Here are a couple of docs from my hooks system.  The first one should be pretty up to date, the second one is woefully incomplete.

man hooks:
Code: [Select]
hooks - description of the Ain Soph hooks mechanism

SYNOPSIS
    Ain Soph supports a generalized mechanism for allowing developer-defined
    code to alter the course of and be notified of lib-handled events.  This
    is referred to as the "hooks" mechanism.

FILES
    /mod/basic/hooks.c
    /lib/hooks.h

DESCRIPTION
    A hook is a way of getting the core mechanics of the game to talk to your
    code when some event it handles occurs, like an object moving or an item
    being equipped or a lock being picked.  The term "hook" comes from the idea
    that the lib is providing a "hook" that you can "hang" your code on.  There
    are many types of hooks, each with a different function; some allow you to
    determine whether an event will be allowed to occur, others allow you to
    alter numerical values like damage amounts and success chances, and others
    simply provide you with notification that an event has occurred.  All hooks
    currently supported by the lib are listed in 'man hook_list'.

    When you define a hook, you send some particular object a hook type and
    the code that the lib should talk to -- generally in the form of a function
    pointer (see 'man function').  You are saying to the lib, "when this event
    happens to the object I am manipulating, talk to this piece of code about
    it".

    The two basic functions required to interact with hooks from a perspective
    of project building are add_hook() and remove_hook().

status add_hook(mixed hook, mixed call)

    This function inserts a hook call into the object's list of hooks.  Since
    add_hook() exists in all characters, items, rooms, and standard daemons,
    any hook can be defined in nearly any object, but only certain object types
    will make use of them, as described in 'man hook_list'.

    The first argument to add_hook() is a hook macro from hooks.h; the hook
    macros and their effects are listed in 'man hook_list'.  While the hook
    macros resolve to integer values, do not ever use a literal integer for
    defining a hook.

    The second argument defines the call you wish to be performed by the hook
    mechanism.  The most common thing to pass here is a closure; e.g.
    add_hook(Can_Unlock, #'my_unlock_hook).  A closure will be simply
    passed the arguments described for the hook in 'man <hook>'.  You may
    also pass an object or a string object filename; using these will result in
    the function resolve_hook() being called in the specified object and passed
    the integer hook type followed by the standard arguments for the hook.
    With improvements in the save mechanics, there is no really good reason to
    use the object/string hook functionality, and it may be removed at some
    point in the future; consider it deprecated.

    add_hook() returns true if the hook was successfully defined.

    set_hook() performs the same function as add_hook(), as a backward
    compatibility function.

status remove_hook(mixed hook, mixed call)

    remove_hook() is used to remove a previously defined hook from an object.
    The arguments must be identical to the ones originally used to define the
    hook.

    closure hit_hook;
    int hits;

    void weapon_hit() {
        hits++;
    }

    void start_counting_hits() {
        add_hook(Mod_Inflict_Damage, #'weapon_hit)
    }

    void stop_counting_hits() {
        remove_hook(Mod_Inflict_Damage, #'weapon_hit)
    }

    void create() {
        start_counting_hits()
        call_out("stop_counting_hits", 300)
    }

    remove_hook() returns true if the hook was successfully removed.

    The other functions related to hooks are query_hook(), any_hook(),
    query_hooks(), and check_hook().

status require_hook(mixed hook, mixed call)
 
    require_hook() adds the specified hook only if it is not already present.
    If the hook was present, it returns True, otherwise it returns the result
    of add_hook().

mixed query_hook(mixed hook)

    query_hook() returns the call information associated with the specified
    hook.  If none are defined, this will be 0.  Otherwise, it may return a
    closure, an object, a string, or an array composed of two or more of
    these.

status any_hook(mixed hook)

    Returns true if the object has any hook information for the hook specified.
    This is intended as the fastest possible test to see if it is useful to
    do a full check of the hook.  The reason to do this is that the mapping
    construction used in specifying hook arguments is fairly expensive.

mapping query_hooks()

    Returns the mapping containing the object's hook information.  A mapping
    is used rather than an array despite the integer hook definitions because
    the hook information is expected to be very sparse in nearly all objects.

varargs mixed check_hook(mixed hook, mixed args, mixed working_value)

    This is the function used by lib modules to determine the effects a hook
    should have.  The first two arguments are the hook to check and the
    arguments to pass to the hook calls.  The third argument is optional and
    is only used by Poll hooks.  The exact behavior of check_hook() depends on
    the type of hook being analyzed:

    For Can hooks, each call is checked in turn until one returns a value
    other than 1 or 0, at which point that value is immediately returned and
    no other calls are checked.  If no such return value is encountered,
    check_hook() returns 0 if any call returned 0, or 1 otherwise.

    For Do, Fail, and At hooks, all calls are checked.  If any call returned
    a value other than 1 or 0, the first such value found is returned.  If no
    such return values are encountered, check_hook() returns 1 if any call
    returned 1, or 0 otherwise.

    For Mod hooks, all calls are checked and their return values are
    accumulated (which will result in an error if any values are of types
    which are not addition-compatible).  The accumulated value is returned.

    For Value hooks, each call is checked in turn until one returns a value
    other than 0, at which points that value is immediately returned and no
    other calls are checked.  If no such return value is encountered,
    check_hook() returns 0.

    For Poll hooks, all calls are checked, and those with nonzero return values
    are considered "votes" for their value.  A return value of ({ 0 }) is
    considered a vote for zero.  Also, if a two-element array with an integer
    second element is returned it is considered a number of votes for the first
    element equal to the second element; i.e. ({ "foo", 3 }) is three votes for
    the value "foo".  For these hooks, the third argument to check_hook(),
    working_value, specifies a value which will be returned if no values are
    obtained from hooks (allowing a result of zero to be distinguished from no
    result).

    For Revise hooks, all calls are checked, with whatever value is returned
    by each hook in turn considered as the desired revised value.  The current
    version of this continually-revised value is passed as the first argument
    to the hook functions, in addition to any normal argument set for the
    hook.

    All lib-level hooks, and nearly all hooks in general, use a mapping for
    their arguments, with various keys as appropriate to their function.  Note,
    however, that if check_hook() receives an array argument, the array will be
    expanded into an argument list as per apply().

DEVELOPMENT CREDITS

    Chaos
        Design and implementation

    Ambidexter
        Implementation work

SEE ALSO
    hook_list(mechanisms)

man hook_list:
Code: [Select]
hook_list - description of specific hooks available in Ain Soph

SYNOPSIS
    This document lists the hooks available via the hooks mechanism, what
    objects acknowledge them, and the arguments provided to them.

FILES
    /mod/basic/hooks.c
    /lib/hooks.h

DESCRIPTION
    There are seven types of hooks: Can, Do, Fail, Mod, Poll, Value, At, and
    Revise hooks.  Each hook begins with one of these classifications.  All
    Can hooks have Do and Fail hooks which corresponds to them.

    Can hooks control whether an event can take place.  In general, they are
    expected to have a return value of 1, for allowing the action to take
    place, 0, for denying the action, or a string or message array, for
    denying the action with the return value providing an error message.
    Any special properties of return values are noted in the description of
    the individual hook.

    IMPORTANT NOTE: It is very important not to *do* things in Can_ hooks.
    That includes issuing messages other than by return value, moving things,
    harming people, or anything else.  If you need to do anything like that,
    and the choice of whether you need to do it has to be made in the Can_
    hook, use a global variable to signal to a corresponding Do_ or Fail_
    hook and execute the action there.  This is VERY IMPORTANT.  The reason
    is that Can_ hooks are SPECIFICALLY MEANT to be able to be called merely
    to *find out* if an action would theoretically be possible.  That means
    that when a Can_ hook is called, you DO NOT KNOW whether the action is
    actually taking place, so having consequences to it occur may be utterly
    inappropriate.

    Do hooks are called to indicate that an event has taken place or, in some
    cases, is imminently about to take place.  Their return values are usually,
    but not always, ignored.  Any action taken based on their return values
    will be specified in the hook's documentation.

    Fail hooks are called to indicate that an event has failed.  Depending
    on the individual implementation, this may mean specifically that a Can
    hook has prevented the event, or other failure modes may trigger the hook.
    As with Do hooks, return values are usually ignored, but if not, this will
    be described in the hook's documentation.

    Mod hooks usually affect the numerical values associated with an event;
    e.g. the amount of damage inflicted by a weapon hit, the chance of success
    at picking a lock or breaking a door.  As they can be used to accumulate
    other addable values, such as arrays, they can also be used in the process
    of building object lists and so forth.

    Value hooks resemble Mod hooks, except that they only allow one value to be
    returned.  They are mainly used by lib core applications.

    Poll hooks resemble Value hooks, but rather than simply using the first
    nonzero value encountered, a "vote" is taken among all the hooks, with the
    value which receives the most "votes" winning.

    At hooks are identical to Do hooks except in that they have no
    corresponding Can and Fail hooks.  They exist so notification hooks
    can be properly defined for events cannot feasibly or appropriatly be
    made preventable.

    Revise hooks are something like Mod or Poll hooks, but work by continually
    modifying a given value.  The return value of each hook is considered to
    be the desired modification of this value.  The current value being
    revised is passed as the first argument to the hook functions, before any
    other arguments normally used by the hook.

    The full list of lib-wide hooks is defined by /lib/hooks.h.  Not all hooks
    defined are necessarily implemented.  Hooks in the following list can be
    considered implemented.  Each has its own man page for further information;
    note that the man pages use the lower case version of the name (so you
    should type e.g. 'man can_act' rather than 'man Can_Act').

    The lib-wide hooks are designated using integer macros.  The hooks support
    also allows strings to be used to designate "local hooks"; this is intended
    to be used by limited-scope applications for which it would not be
    appropriate to define a lib-wide hook.  Hooks of this type should use a
    name that looks like "can_xxx_yyy", "do_xxx_yyy", and so on, where xxx is
    an indicator of the context (e.g. "ring", "figurine", "Project_Monster")
    and yyy is a description of the action of the hook.  The type of a local
    hook is determined by the letter the name begins with:

        'a' : At hook
        'c' : Can hook
        'd' : Do hook
        'f' : Fail hook
        'm' : Mod hook
        'p' : Poll hook
        'v' : Value hook

    Hooks beginning with other letters are also treated as Mod hooks, but it
    is not recommended that you take advantage of this, as it could change.

    Can_Act
    Can_Ammo_Launch_Move
    Can_Attack
    Can_Attack_In
    Can_Be_Attacked
    Can_Be_Closed
    Can_Be_Friend
    Can_Be_Linkdead
    Can_Be_Locked
    Can_Be_Opened
    Can_Be_Unlocked
    Can_Block_Exit
    Can_Die
    Can_Equip_Item
    Can_Fight
    Can_Go_Direction
    Can_Have_Limb_Disabled
    Can_Have_Limb_Restored
    Can_Have_Limb_Severed
    Can_Have_Lock_Picked
    Can_Hear
    Can_Heart_Beat
    Can_Launch_Move
    Can_Look_Direction
    Can_Move
    Can_Move_In
    Can_Move_Out
    Can_Obey
    Can_Perform_Close
    Can_Perform_Lock
    Can_Perform_Obey
    Can_Perform_Open
    Can_Perform_Pick_Lock
    Can_Perform_Unlock
    Can_Quit
    Can_Unequip_Item

    Do_Act
    Do_Ammo_Launch_Move
    Do_Attack
    Do_Attack_In
    Do_Be_Attacked
    Do_Be_Closed
    Do_Be_Friend
    Do_Be_Linkdead
    Do_Be_Locked
    Do_Be_Opened
    Do_Be_Unlocked
    Do_Block_Exit
    Do_Die
    Do_Equip_Item
    Do_Fight
    Do_Go_Direction
    Do_Have_Limb_Disabled
    Do_Have_Limb_Restored
    Do_Have_Limb_Severed
    Do_Have_Lock_Picked
    Do_Hear
    Do_Heart_Beat
    Do_Launch_Move
    Do_Look_Direction
    Do_Move
    Do_Move_In
    Do_Move_Out
    Do_Obey
    Do_Perform_Close
    Do_Perform_Lock
    Do_Perform_Obey
    Do_Perform_Open
    Do_Perform_Pick_Lock
    Do_Perform_Unlock
    Do_Quit
    Do_Unequip_Item

    Fail_Act
    Fail_Attack
    Fail_Attack_In
    Fail_Be_Attacked
    Fail_Be_Closed
    Fail_Be_Friend
    Fail_Be_Locked
    Fail_Be_Opened
    Fail_Be_Unlocked
    Fail_Block_Exit
    Fail_Die
    Fail_Equip_Item
    Fail_Fight
    Fail_Go_Direction
    Fail_Have_Limb_Disabled
    Fail_Have_Limb_Restored
    Fail_Have_Limb_Severed
    Fail_Have_Lock_Picked
    Fail_Hear
    Fail_Heart_Beat
    Fail_Obey
    Fail_Perform_Close
    Fail_Perform_Lock
    Fail_Perform_Obey
    Fail_Perform_Open
    Fail_Perform_Pick_Lock
    Fail_Perform_Unlock
    Fail_Unequip_Item

    Mod_Absorb_Damage
    Mod_Ammo_Inflict_Damage
    Mod_Attack_Cycle
    Mod_Comprehend_Language
    Mod_Destination
    Mod_Friendship
    Mod_Inflict_Damage
    Mod_Obedience
    Mod_Pick_Lock

    At_Update_Configuration
    At_Incarnate

Offline wodan

  • BFF
  • ***
  • Posts: 434
  • Drink and code, you know you want to!
    • View Profile
Re: heart_beat v. call_out
« Reply #11 on: April 26, 2010, 04:50:32 PM »
interesting, i'm thinking of adding inotify support :)
hmm, no need, just use inotifywait as an external command :)

Offline torne

  • Acquaintance
  • *
  • Posts: 1
    • View Profile
Re: heart_beat v. call_out
« Reply #12 on: August 22, 2010, 05:16:14 PM »
Does anyone have some good test cases for heartbeat/callout performance? There's some changes to the FluffOS implementations I'd like to try, but not sure what's a realistic benchmark.