Enhance threadsafety of Hooks and improve generally
[clinton/bobotpp.git] / source / BotInterp.C
dissimilarity index 62%
index 815c9bf..de65e93 100644 (file)
-// BotInterp.C  -*- C++ -*-
-// Copyright (c) 1998 Etienne BERNARD
-// Copyright (C) 2002 Clinton Ebadi
-
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "Utils.H"
-#include "Bot.H"
-#include "BotInterp.H"
-
-#ifdef USESCRIPTS
-
-#include <libguile.h>
-extern "C"
-{
-#include <libguile/regex-posix.h>
-}
-
-BotInterp::BotInterp(Bot *b, String fn)
-  : bot(b), counter(0)
-{
-  logPort = scm_open_file(Utils::str2scm (fn),
-                          Utils::str2scm ("a"));
-  scm_gc_protect_object(logPort);
-}
-
-void
-BotInterp::Execute(String command)
-{
-  Interp::Execute(bot, command);
-}
-
-void
-BotInterp::LoadScript(String filename)
-{
-  Interp::LoadScript(bot, filename);
-}
-
-void
-BotInterp::ScriptLog(SCM throw_args)
-{
-  scm_display_error_message(SCM_CADR (throw_args),
-                            SCM_CADDR (throw_args),
-                            logPort);
-  scm_flush(logPort);
-}
-
-namespace
-{
-  bool hptr_lt (const Hook* h, const Hook* h1)
-    // Hook Pointer less than
-    // Used to sort the Hooks list
-  { return *h < *h1; }
-}
-
-bool
-BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall,
-                  String name) {
-  if (scm_string_p(regex) == SCM_BOOL_F)
-    return false;
-  String rx = Utils::to_upper (Utils::scm2str (regex));
-  SCM r = scm_make_regexp(regex,
-                          scm_listify (gh_lookup("regexp/icase"),
-                                  SCM_UNDEFINED));
-  scm_gc_protect_object(r);
-  scm_gc_protect_object(function);
-  // First, we check if an hook doesn't exist yet
-  std::list<Hook *>::iterator it = hooksMap[hooktype].begin();
-  std::list<Hook *>::iterator it2 = hooksMap[hooktype].end();
-
-  for ( ; it != it2; ++it)
-    // It exists, we replace it.
-    if ((*it)->regex_str == rx && (*it)->name == name) {
-      scm_gc_unprotect_object((*it)->function);
-      scm_gc_unprotect_object (r);
-      (*it)->function = function;
-      (*it)->priority = pri;
-      (*it)->fallthru = fall;
-      hooksMap[hooktype].sort (hptr_lt);
-      return true;
-    }
-  // It does not exist, we create it
-  hooksMap[hooktype].push_back (new Hook(hooktype, rx, r, 
-                                        function, pri, fall, name));
-  hooksMap[hooktype].sort (hptr_lt);
-  return true;
-}
-
-bool
-BotInterp::RunHooks(int hooktype, String match, SCM args)
-{
-  SCM result;
-  // We want to execute higher priority hooks first, so we start at
-  // the end of the list instead of the beggining
-  std::list<Hook *>::reverse_iterator it = hooksMap[hooktype].rbegin();
-  std::list<Hook *>::reverse_iterator it2 = hooksMap[hooktype].rend();
-  wrapper_data wd;
-  wd.args = args;
-  for ( ; it != it2; ++it) {
-    if (scm_regexp_exec((*it)->regex, Utils::str2scm (match),
-                        SCM_UNDEFINED, SCM_UNDEFINED) != SCM_BOOL_F)
-      {
-       wd.func = (*it)->function;
-       result = gh_catch(SCM_BOOL_T, 
-                         (scm_t_catch_body) scm_apply_wrapper,
-                         static_cast<void *> (&wd), 
-                         (scm_t_catch_handler) Interp::ErrorHandler, 0);
-       if (! (*it)->fallthru)
-         break;
-    }
-  }
-  return true;
-}
-
-SCM
-BotInterp::AddTimer(int delay, SCM function)
-{
-  int when = time(NULL) + delay;
-  int c = ++counter;
-  scm_gc_protect_object(function);
-  Timer *t = new Timer(c, when, function);
-  timersList.push_back(t);
-  return scm_long2num (c);
-}
-
-bool
-BotInterp::DelTimer(SCM timer)
-{
-  int count = scm_num2long(timer, SCM_ARG1, "BotInterp::DelTimer");
-  std::list<Timer *>::iterator it = timersList.begin();
-  std::list<Timer *>::iterator it2 = timersList.end();
-
-  for ( ; it != it2; ++it) {
-    if ((*it)->count == count) {
-      scm_gc_unprotect_object((*it)->function);
-      delete (*it);
-      timersList.erase(it);
-      return true;
-    }
-  }
-  return false;
-}
-
-bool
-BotInterp::RunTimers(int now)
-{
-  std::list<Timer *>::iterator it = timersList.begin();
-  std::list<Timer *>::iterator it2 = timersList.end();
-  std::list<Timer *>::iterator it3;
-
-  struct wrapper_data wd;
-  wd.args = scm_listify (SCM_UNDEFINED);
-
-  while (it != it2) {
-    if ((*it)->when <= now) {
-      wd.func = (*it)->function;
-      gh_catch(SCM_BOOL_T, (scm_t_catch_body) scm_apply_wrapper,
-               (void *)&wd, (scm_t_catch_handler) Interp::ErrorHandler, 0);
-      scm_gc_unprotect_object(wd.func);
-      it3 = it;
-      ++it3;
-      delete (*it);
-      timersList.erase(it);
-      it = it3;
-    } else {
-      ++it;
-    }
-  }
-  return true;
-}
-
-#endif
+// BotInterp.C  -*- C++ -*-
+// Copyright (c) 1998 Etienne BERNARD
+// Copyright (C) 2002,2005,2008 Clinton Ebadi
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+// 02110-1301, USA.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <algorithm>
+
+#include "Utils.H"
+#include "Bot.H"
+#include "BotInterp.H"
+
+#ifdef USESCRIPTS
+
+#include <libguile.h>
+extern "C"
+{
+#include <libguile/regex-posix.h>
+}
+
+bool
+Hook::operator< (const Hook & h) const
+{
+  if (priority < h.priority)
+    {
+      return true;
+    }
+  else if (priority > h.priority)
+    {
+      return false;
+    }
+  else if (fallthru && h.fallthru)
+    {
+      return false;
+    }
+  else if (fallthru && !h.fallthru)
+    {
+      return false;
+    }
+  else if (!fallthru && h.fallthru)
+    {
+      return true;
+    }
+  else
+    {
+      // NOTE: This should never be reached
+      return false;
+    }
+}
+
+BotInterp::BotInterp(Bot *b, String fn)
+  : bot(b), counter(0), 
+    hook_mutex (true), timer_mutex (true)
+{
+  logPort = scm_open_file (Utils::str2scm (fn),
+                           Utils::str2scm ("a"));
+
+  scm_gc_protect_object(logPort);
+}
+
+void
+BotInterp::Execute(String command)
+{
+  Interp::Execute(bot, command);
+}
+
+void
+BotInterp::LoadScript(String filename)
+{
+  Interp::LoadScript(bot, filename);
+}
+
+SCM
+BotInterp::ScriptLog()
+{
+  return logPort;
+}
+
+namespace
+{
+  struct HookFind
+  {
+    std::string rx;
+    std::string name;
+
+    HookFind (std::string r, std::string n)
+      : rx (r), name (n)
+    { }
+
+    bool operator() (const Hook * hook) const
+    { return hook->regex_str == rx && hook->name == name; }
+  };
+}
+
+bool
+BotInterp::AddHook(int hooktype, SCM regex, SCM function, int pri, bool fall,
+                  std::string name) 
+{
+  if (scm_string_p(regex) == SCM_BOOL_F)
+    return false;
+
+  BotLock hook_lock (hook_mutex);
+  std::string rx = Utils::to_upper (Utils::scm2str (regex));
+  SCM r = scm_make_regexp (regex,
+                          scm_list_n (scm_variable_ref (scm_c_lookup ("regexp/icase")),
+                                      SCM_UNDEFINED));
+  HookFind hook_find (rx, name);
+  HookList& hook_list = hooks[hooktype];
+
+  scm_gc_protect_object(r);
+  scm_gc_protect_object(function);
+
+  HookList::iterator it = std::find_if (hook_list.begin (),
+                                       hook_list.end (),
+                                       hook_find);
+                                                
+  if (it != hook_list.end())
+    {
+      Hook * found = *it;
+
+      scm_gc_unprotect_object(found->function);
+      scm_gc_unprotect_object (r);
+
+      found->function = function;
+      found->priority = pri;
+      found->fallthru = fall;
+
+      hook_list.erase (it);
+      Utils::push_sorted (hook_list, found, hook_sort_p);
+
+      return true;
+    }
+  else
+    {
+      Utils::push_sorted (hook_list,
+                         new Hook(hooktype, rx, r, function, pri, fall, name),
+                         hook_sort_p);
+      
+      return true;
+    }
+}
+
+bool
+BotInterp::RunHooks(int hooktype, std::string match, SCM args)
+{
+  BotLock hook_lock (hook_mutex);
+
+  SCM result;
+  wrapper_data wd;
+  wd.args = args;
+
+  // We want to execute higher priority hooks first, so we start at
+  // the end of the list instead of the beginning
+  
+  for (HookList::reverse_iterator it = hooks[hooktype].rbegin();
+       it != hooks[hooktype].rend();
+       ++it) 
+    {
+      std::cerr << "Matching...\n";
+      if (scm_regexp_exec((*it)->regex, Utils::str2scm (match),
+                         SCM_UNDEFINED, SCM_UNDEFINED) != SCM_BOOL_F)
+       {
+         std::cerr << " Match " << (*it)->regex_str << std::endl;
+         bool fallthru_p = (*it)->fallthru;
+         wd.func = (*it)->function;
+         result = scm_internal_catch(SCM_BOOL_T, 
+                                     (scm_t_catch_body) 
+                                     Interp::LazyApplyWrapper,
+                                     static_cast<void *> (&wd), 
+                                     (scm_t_catch_handler) Interp::EmptyHandler, 0);
+         if (!fallthru_p)
+           break;
+       }
+    }
+  
+  return true;
+}
+
+SCM
+BotInterp::AddTimer(int delay, SCM function)
+{
+  BotLock timer_lock (timer_mutex);  
+  int when = time(NULL) + delay;
+
+  scm_gc_protect_object(function);
+
+  Timer *timer = new Timer (++counter, when, function);
+  Utils::push_sorted (timers, timer, timer_sort_p);
+
+  return scm_from_int (counter);
+
+}
+
+bool
+BotInterp::DelTimer(SCM timer)
+{
+  BotLock timer_lock (timer_mutex);
+
+  int count = scm_to_int (timer);
+  TimerList::iterator it = timers.begin();
+  TimerList::iterator end = timers.end();
+
+  for ( ; it != end; ++it) 
+    {
+      if ((*it)->count == count) 
+       {
+         scm_gc_unprotect_object((*it)->function);
+         delete (*it);
+         timers.erase(it);
+
+         return true;
+       }
+    }
+
+  return false;
+}
+
+bool
+BotInterp::RunTimers(int now)
+{
+  BotLock timer_lock (timer_mutex);
+  struct wrapper_data wd;
+  wd.args = scm_list_n (SCM_UNDEFINED);
+
+  while (!timers.empty ())
+    {
+      // Keep a stack allocated copy of the front of the timer queue
+      // just in case the timer is deleted while being executed (which
+      // is very unlikely as the only place this could occur is if the
+      // timer deleted itself)
+      Timer current_timer = *timers.front () ; 
+
+      if (current_timer.when <= now) 
+       {
+         wd.func = current_timer.function;
+
+         scm_internal_catch (SCM_BOOL_T,
+                             (scm_t_catch_body) Interp::LazyApplyWrapper,
+                             (void *)&wd,
+                             (scm_t_catch_handler) Interp::EmptyHandler, 
+                             0);
+
+         // The timer list may have been modified by the timer
+         // callback; if it has in such a way that the first queue
+         // item has changed (adding a timer in the past) then we
+         // switch the slow path for deleting a timer
+         if (current_timer.count == timers.front()->count)
+           {
+             scm_gc_unprotect_object (current_timer.function);
+             delete timers.front ();
+             timers.pop_front ();
+           }
+         else
+           {
+             DelTimer (scm_from_int (current_timer.count));
+           }
+       } 
+      else 
+       {
+         break;
+       }
+    }
+
+  return true;
+}
+
+#endif