exec_thread.cpp

00001 
00002 /***************************************************************************
00003  *  exec_thread.cpp - Fawkes Skiller: Execution Thread
00004  *
00005  *  Created: Mon Feb 18 10:30:17 2008
00006  *  Copyright  2006-2009  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include "exec_thread.h"
00024 
00025 #include <core/exceptions/software.h>
00026 #include <core/exceptions/system.h>
00027 #include <core/threading/mutex.h>
00028 #include <utils/logging/component.h>
00029 #ifdef SKILLER_TIMETRACKING
00030 #  include <utils/time/tracker.h>
00031 #endif
00032 
00033 #include <lua/context.h>
00034 #include <lua/interface_importer.h>
00035 
00036 #include <interfaces/SkillerInterface.h>
00037 #include <interfaces/SkillerDebugInterface.h>
00038 
00039 #include <lua.hpp>
00040 #include <tolua++.h>
00041 
00042 #include <string>
00043 #include <cstring>
00044 
00045 using namespace std;
00046 using namespace fawkes;
00047 
00048 /** @class SkillerExecutionThread "exec_thread.h"
00049  * Skiller Execution Thread.
00050  * This thread runs and controls the Lua interpreter and passes data into the
00051  * execution engine.
00052  *
00053  * @author Tim Niemueller
00054  */
00055 
00056 /** Constructor. */
00057 SkillerExecutionThread::SkillerExecutionThread()
00058   : Thread("SkillerExecutionThread", Thread::OPMODE_WAITFORWAKEUP),
00059     BlockedTimingAspect(BlockedTimingAspect::WAKEUP_HOOK_SKILL),
00060     BlackBoardInterfaceListener("SkillerExecutionThread")
00061 {
00062   __continuous_run   = false;
00063   __continuous_reset = false;
00064   __error_written    = false;
00065 
00066   __lua = NULL;
00067 }
00068 
00069 
00070 /** Destructor. */
00071 SkillerExecutionThread::~SkillerExecutionThread()
00072 {
00073 }
00074 
00075 /** Clean up when init failed.
00076  * You may only call this from init(). Never ever call it from anywhere
00077  * else!
00078  */
00079 void
00080 SkillerExecutionThread::init_failure_cleanup()
00081 {
00082   try {
00083     if ( __skiller_if ) blackboard->close(__skiller_if);
00084     if ( __skdbg_if )   blackboard->close(__skdbg_if);
00085 
00086     delete __lua_ifi;
00087     delete __clog;
00088 
00089   } catch (...) {
00090     // we really screwed up, can't do anything about it, ignore error, logger is
00091     // initialized since this method is only called from init() which is only called if
00092     // all aspects had been initialized successfully
00093     logger->log_error(name(), "Really screwed up while finalizing, aborting cleanup. "
00094                               "Fawkes is no longer in a clean state. Restart!");
00095   }
00096 }
00097 
00098 
00099 void
00100 SkillerExecutionThread::init()
00101 {
00102   __last_exclusive_controller = 0;
00103   __reader_just_left = false;
00104   __continuous_run   = false;
00105   __continuous_reset = false;
00106   __skdbg_what = "ACTIVE";
00107   __skdbg_graphdir = "TB";
00108   __skdbg_graphcolored = true;
00109   __clog = NULL;
00110   __sksf_pushed = false;
00111 
00112   try {
00113     __cfg_skillspace  = config->get_string("/skiller/skillspace");
00114     __cfg_watch_files = config->get_bool("/skiller/watch_files");
00115   } catch (Exception &e) {
00116     e.append("Insufficient configuration for Skiller");
00117     throw;
00118   }
00119 
00120   logger->log_debug("SkillerExecutionThread", "Skill space: %s", __cfg_skillspace.c_str());
00121 
00122   __clog = new ComponentLogger(logger, "SkillerLua");
00123 
00124   __lua = NULL;
00125   __lua_ifi = NULL;
00126   __skiller_if = NULL;
00127   __skdbg_if = NULL;
00128 
00129   std::string reading_prefix = "/skiller/interfaces/" + __cfg_skillspace + "/reading/";
00130   std::string writing_prefix = "/skiller/interfaces/" + __cfg_skillspace + "/writing/";
00131 
00132   try {
00133     __skiller_if = blackboard->open_for_writing<SkillerInterface>("Skiller");
00134     __skdbg_if   = blackboard->open_for_writing<SkillerDebugInterface>("Skiller");
00135     
00136     __lua  = new LuaContext(__cfg_watch_files);
00137 
00138     __lua_ifi = new LuaInterfaceImporter(__lua, blackboard, config, logger);
00139     __lua_ifi->open_reading_interfaces(reading_prefix);
00140     __lua_ifi->open_writing_interfaces(writing_prefix);
00141     __lua_ifi->add_interface("skdbg", __skdbg_if);
00142     __lua_ifi->add_interface("skiller", __skiller_if);
00143 
00144     __lua->add_package_dir(LUADIR);
00145     __lua->add_cpackage_dir(LUALIBDIR);
00146 
00147     __lua->add_package("fawkesutils");
00148     __lua->add_package("fawkesconfig");
00149     __lua->add_package("fawkesinterface");
00150     __lua->add_package("fawkesgeometry");
00151 
00152     __lua->set_string("SKILLSPACE", __cfg_skillspace.c_str());
00153     __lua->set_usertype("config", config, "Configuration", "fawkes");
00154     __lua->set_usertype("logger", __clog, "ComponentLogger", "fawkes");
00155     __lua->set_usertype("clock", clock, "Clock", "fawkes");
00156 
00157     __lua_ifi->push_interfaces();
00158 
00159     __lua->set_start_script(LUADIR"/skiller/start.lua");
00160   
00161     __skiller_if->set_skill_string("");
00162     __skiller_if->set_status(SkillerInterface::S_INACTIVE);
00163     __skiller_if->write();
00164 
00165     __skdbg_if->set_graph("");
00166     __skdbg_if->set_graph_fsm("ACTIVE");
00167 
00168   } catch (Exception &e) {
00169     init_failure_cleanup();
00170     throw;
00171   }
00172 
00173   // We want to know if our reader leaves and closes the interface
00174   bbil_add_reader_interface(__skiller_if);
00175   blackboard->register_listener(this, BlackBoard::BBIL_FLAG_READER);
00176 
00177 #ifdef SKILLER_TIMETRACKING
00178   __tt           = new TimeTracker();
00179   __ttc_total    = __tt->add_class("Total");
00180   __ttc_msgproc  = __tt->add_class("Message Processing");
00181   __ttc_luaprep  = __tt->add_class("Lua Preparation");
00182   __ttc_luaexec  = __tt->add_class("Lua Execution");
00183   __ttc_publish  = __tt->add_class("Publishing");
00184   __tt_loopcount = 0;
00185 #endif
00186 }
00187 
00188 
00189 void
00190 SkillerExecutionThread::finalize()
00191 {
00192 #ifdef SKILLER_TIMETRACKING
00193   delete __tt;
00194 #endif
00195   delete __lua_ifi;
00196 
00197   blackboard->unregister_listener(this);
00198   blackboard->close(__skiller_if);
00199   blackboard->close(__skdbg_if);
00200 
00201   delete __lua;
00202   delete __clog;
00203 }
00204 
00205 
00206 void
00207 SkillerExecutionThread::bb_interface_reader_removed(Interface *interface,
00208                                                   unsigned int instance_serial) throw()
00209 {
00210   if ( instance_serial == __skiller_if->exclusive_controller() ) {
00211     logger->log_debug("SkillerExecutionThread", "Controlling interface instance was closed, "
00212                       "revoking exclusive control");
00213 
00214     __last_exclusive_controller = instance_serial;
00215     __reader_just_left = true;
00216 
00217     __skiller_if->set_exclusive_controller(0);
00218     __skiller_if->write();
00219   }
00220 }
00221 
00222 
00223 
00224 /** Determines the skill status and writes it to the BB.
00225  * This method assumes that it is called from within loop() and lua_mutex is locked.
00226  * @param curss current skill string
00227  */
00228 void
00229 SkillerExecutionThread::publish_skill_status(std::string &curss)
00230 {
00231   //const char *sst = "Unknown";
00232   LUA_INTEGER running = 0, final = 0, failed = 0;
00233 
00234   SkillerInterface::SkillStatusEnum old_status = __skiller_if->status();
00235   SkillerInterface::SkillStatusEnum new_status = __skiller_if->status();
00236 
00237   try {
00238 
00239     if ( curss == "" ) {
00240       // nothing running, we're inactive
00241       //sst = "S_INACTIVE/empty";
00242       __skiller_if->set_status(SkillerInterface::S_INACTIVE);
00243 
00244     } else {                                  // Stack:
00245       __lua->get_global("skillenv");          // skillenv
00246 
00247       __lua->get_field(-1, "get_status");     // skillenv skillenv.get_status
00248       if ( __lua->is_function(-1) ) {
00249         __lua->pcall(0, 3);                   // skillenv running final failed
00250         running = __lua->to_integer(-3);
00251         final   = __lua->to_integer(-2);
00252         failed  = __lua->to_integer(-1);
00253 
00254         __lua->pop(4);                        // ---
00255       } else {
00256         __lua->pop(2);                        // ---
00257         throw LuaRuntimeException("C++:publish_skill_status", "skillenv.get_status is not a function");
00258       }
00259 
00260       if ( failed > 0 ) {
00261         //sst = "S_FAILED";
00262         new_status = SkillerInterface::S_FAILED;
00263       } else if ( (final > 0) && (running == 0) ) {
00264         //sst = "S_FINAL";
00265         new_status = SkillerInterface::S_FINAL;
00266       } else if ( running > 0 ) {
00267         //sst = "S_RUNNING";
00268         new_status = SkillerInterface::S_RUNNING;
00269       } else {
00270         // all zero
00271         //sst = "S_INACTIVE";
00272         new_status = SkillerInterface::S_INACTIVE;
00273       }
00274     }
00275 
00276     if ( (old_status != new_status) ||
00277          (curss != __skiller_if->skill_string()) ||
00278          (__skiller_if->is_continuous() != __continuous_run) ) {
00279 
00280       /*
00281       logger->log_debug("SkillerExecutionThread", "Status is %s (%i vs. %i)"
00282                         "(running %i, final: %i, failed: %i)",
00283                         sst, old_status, new_status, running, final, failed);
00284       */
00285 
00286       __skiller_if->set_skill_string(curss.c_str());
00287       __skiller_if->set_continuous(__continuous_run);
00288 
00289       __skiller_if->set_status(new_status);
00290 
00291       if ( ! __error_written && (new_status == SkillerInterface::S_FAILED) ) {
00292         publish_error();
00293         __error_written = __continuous_run;
00294       } else if (new_status == SkillerInterface::S_RUNNING ||
00295                  new_status == SkillerInterface::S_FINAL) {
00296         __skiller_if->set_error("");
00297         __error_written = false;
00298       }
00299 
00300       __skiller_if->write();
00301     }
00302 
00303   } catch (Exception &e) {
00304     logger->log_error("SkillerExecutionThread", "Failed to retrieve skill status");
00305     logger->log_error("SkillerExecutionThread", e);
00306     try {
00307       __skiller_if->set_status(SkillerInterface::S_FAILED);
00308     } catch (Exception &e2) {
00309       logger->log_error("SkillerExecutionThread", "Failed to set FAILED as skill "
00310                         "status value during error handling");
00311       logger->log_error("SkillerExecutionThread", e2);
00312     }
00313   }
00314 
00315 }
00316 
00317 
00318 void
00319 SkillerExecutionThread::publish_skdbg()
00320 {
00321   try {
00322     __lua->do_string("skillenv.write_skiller_debug(interfaces.writing.skdbg, \"%s\", \"%s\", %s)",
00323                      __skdbg_what.c_str(), __skdbg_graphdir.c_str(),
00324                      __skdbg_graphcolored ? "true" : "false");
00325   } catch (Exception &e) {
00326     logger->log_warn("SkillerExecutionThread", "Error writing graph");
00327     logger->log_warn("SkillerExecutionThread", e);
00328   }
00329 }
00330 
00331 void
00332 SkillerExecutionThread::lua_loop_reset()
00333 {
00334   try {
00335     __lua->do_string("skillenv.reset_loop()");
00336   } catch (Exception &e) {
00337     logger->log_warn("SkillerExecutionThread", "Lua Loop Reset failed");
00338     logger->log_warn("SkillerExecutionThread", e);
00339   }
00340 }
00341 
00342 
00343 void
00344 SkillerExecutionThread::publish_error()
00345 {
00346   try {
00347     __lua->do_string("skillenv.write_fsm_error(skillenv.get_skill_fsm(skillenv.get_active_skills()), interfaces.writing.skiller)");
00348   } catch (Exception &e) {
00349     logger->log_warn("SkillerExecutionThread", "Error writing error");
00350     logger->log_warn("SkillerExecutionThread", e);
00351     __skiller_if->set_error("Failed to set Lua error");
00352     __skiller_if->write();
00353   }
00354 }
00355 
00356 
00357 void
00358 SkillerExecutionThread::process_skdbg_messages()
00359 {
00360   while ( ! __skdbg_if->msgq_empty() ) {
00361     if ( __skdbg_if->msgq_first_is<SkillerDebugInterface::SetGraphMessage>() ) {
00362       SkillerDebugInterface::SetGraphMessage *m = __skdbg_if->msgq_first<SkillerDebugInterface::SetGraphMessage>();
00363       logger->log_warn(name(), "Setting skiller debug what to: %s", m->graph_fsm());
00364       __skdbg_what = m->graph_fsm();
00365     } else if (__skdbg_if->msgq_first_is<SkillerDebugInterface::SetGraphDirectionMessage>() ) {
00366       SkillerDebugInterface::SetGraphDirectionMessage *m = __skdbg_if->msgq_first<SkillerDebugInterface::SetGraphDirectionMessage>();
00367       switch (m->graph_dir()) {
00368       case SkillerDebugInterface::GD_BOTTOM_TOP:  __skdbg_graphdir = "BT"; break;
00369       case SkillerDebugInterface::GD_LEFT_RIGHT:  __skdbg_graphdir = "LR"; break;
00370       case SkillerDebugInterface::GD_RIGHT_LEFT:  __skdbg_graphdir = "RL"; break;
00371       default:                                    __skdbg_graphdir = "TB"; break;
00372       }
00373 
00374     } else if (__skdbg_if->msgq_first_is<SkillerDebugInterface::SetGraphColoredMessage>() ) {
00375       SkillerDebugInterface::SetGraphColoredMessage *m = __skdbg_if->msgq_first<SkillerDebugInterface::SetGraphColoredMessage>();
00376       __skdbg_graphcolored = m->is_graph_colored();
00377     }
00378 
00379     __skdbg_if->msgq_pop();
00380   }
00381 }
00382 
00383 
00384 void
00385 SkillerExecutionThread::loop()
00386 {
00387 #ifdef SKILLER_TIMETRACKING
00388   __tt->ping_start(__ttc_total);
00389 #endif
00390 #ifdef HAVE_INOTIFY
00391   __lua->process_fam_events();
00392 #endif
00393   __lua_ifi->read();
00394 
00395   // Current skill string
00396   std::string curss = "";
00397 
00398   unsigned int excl_ctrl   = __skiller_if->exclusive_controller();
00399   bool write_skiller_if    = false;
00400   bool last_was_continuous = __continuous_run;
00401 
00402 #ifdef SKILLER_TIMETRACKING
00403   __tt->ping_start(__ttc_msgproc);
00404 #endif
00405   process_skdbg_messages();
00406 
00407   while ( ! __skiller_if->msgq_empty() ) {
00408     if ( __skiller_if->msgq_first_is<SkillerInterface::AcquireControlMessage>() ) {
00409       Message *m = __skiller_if->msgq_first();
00410       if ( excl_ctrl == 0 ) {
00411         logger->log_debug("SkillerExecutionThread", "%s is new exclusive controller",
00412                           m->sender_thread_name());
00413         __skiller_if->set_exclusive_controller(m->sender_id());
00414         write_skiller_if = true;
00415         excl_ctrl = m->sender_id();
00416       } else {
00417         logger->log_warn("SkillerExecutionThread", "%s tried to acquire exclusive control, "
00418                          "but another controller exists already", m->sender_thread_name());
00419       }
00420 
00421     } else if ( __skiller_if->msgq_first_is<SkillerInterface::ReleaseControlMessage>() ) {
00422       Message *m = __skiller_if->msgq_first();
00423       if ( excl_ctrl == m->sender_id() ) {
00424         logger->log_debug("SkillerExecutionThread", "%s releases exclusive control",
00425                           m->sender_thread_name());
00426         
00427         if ( __continuous_run ) {
00428           __continuous_run = false;
00429           __continuous_reset = true;
00430         }
00431         __last_exclusive_controller = __skiller_if->exclusive_controller();
00432         __skiller_if->set_exclusive_controller(0);
00433         write_skiller_if = true;
00434         excl_ctrl = 0;
00435     } else {
00436         if ( !__reader_just_left || (m->sender_id() != __last_exclusive_controller)) {
00437           logger->log_warn("SkillerExecutionThread", "%s tried to release exclusive control, "
00438                            "it's not the controller", m->sender_thread_name());
00439         }
00440       }
00441     } else if ( __skiller_if->msgq_first_is<SkillerInterface::ExecSkillMessage>() ) {
00442       SkillerInterface::ExecSkillMessage *m = __skiller_if->msgq_first<SkillerInterface::ExecSkillMessage>();
00443 
00444       if ( m->sender_id() == excl_ctrl ) {
00445         if ( curss != "" ) {
00446           logger->log_warn("SkillerExecutionThread", "More than one skill string enqueued, "
00447                            "ignoring previous string (%s).", curss.c_str());
00448         }
00449         logger->log_debug("SkillerExecutionThread", "%s wants me to execute '%s'",
00450                           m->sender_thread_name(), m->skill_string());
00451 
00452         if ( __continuous_run ) {
00453           __continuous_run = false;
00454           __continuous_reset = true;
00455         }
00456         curss = m->skill_string();
00457       } else {
00458         logger->log_debug("SkillerExecutionThread", "%s tries to exec while not controller",
00459                           m->sender_thread_name());
00460       }
00461 
00462     } else if ( __skiller_if->msgq_first_is<SkillerInterface::ExecSkillContinuousMessage>() ) {
00463       SkillerInterface::ExecSkillContinuousMessage *m = __skiller_if->msgq_first<SkillerInterface::ExecSkillContinuousMessage>();
00464 
00465       if ( m->sender_id() == excl_ctrl ) {
00466         if ( curss != "" ) {
00467           logger->log_warn("SkillerExecutionThread", "More than one skill string enqueued, "
00468                            "ignoring successive string (%s).", m->skill_string());
00469         } else {          
00470           logger->log_debug("SkillerExecutionThread", "%s wants me to continuously execute '%s'",
00471                             m->sender_thread_name(), m->skill_string());
00472 
00473           curss = m->skill_string();
00474           __continuous_reset = last_was_continuous; // reset if cont exec was in progress
00475           __continuous_run = true;
00476         }
00477       } else {
00478         logger->log_debug("SkillerExecutionThread", "%s tries to exec while not controller",
00479                           m->sender_thread_name());
00480       }
00481 
00482     } else if ( __skiller_if->msgq_first_is<SkillerInterface::StopExecMessage>() ) {
00483       SkillerInterface::StopExecMessage *m = __skiller_if->msgq_first<SkillerInterface::StopExecMessage>();
00484 
00485       if ( (m->sender_id() == excl_ctrl) ||
00486            (__reader_just_left && (m->sender_id() == __last_exclusive_controller)) ) {
00487         logger->log_debug("SkillerExecutionThread", "Stopping continuous execution");
00488         if ( __continuous_run ) {
00489           __continuous_run = false;
00490           __continuous_reset = true;
00491           curss = "";
00492         }
00493       } else {
00494         logger->log_debug("SkillerExecutionThread", "%s tries to stop exec while not controller",
00495                           m->sender_thread_name());
00496       }
00497     } else {
00498       logger->log_warn("SkillerExecutionThread", "Unhandled message of type %s in "
00499                        "skiller interface", __skiller_if->msgq_first()->type());
00500     }
00501 
00502     __skiller_if->msgq_pop();
00503   }
00504 
00505   if ( __continuous_run && (curss == "") ) {
00506     curss = __skiller_if->skill_string();
00507   }
00508 
00509 #ifdef SKILLER_TIMETRACKING
00510   __tt->ping_end(__ttc_msgproc);
00511 #endif
00512 
00513   if ( __continuous_reset ) {
00514     logger->log_debug("SkillerExecutionThread", "Continuous reset forced");    try {
00515       if (__sksf_pushed) {
00516         __sksf_pushed = false;
00517         __lua->pop(1);                    // ---
00518       }
00519       __lua->do_string("skillenv.reset_all()");
00520     } catch (Exception &e) {
00521       logger->log_warn("SkillerExecutionThread", "Caught exception while resetting skills, ignored, output follows");
00522       logger->log_warn("SkillerExecutionThread", e);
00523     }
00524 
00525     __skiller_if->set_status(SkillerInterface::S_INACTIVE);
00526     __skiller_if->set_skill_string("");
00527 
00528     //We're not resetting, because this is information someone might need...
00529     //__skiller_if->set_error("");
00530     __error_written    = false;
00531     __continuous_reset = false;
00532     write_skiller_if   = true;
00533   }
00534 
00535   if ( write_skiller_if )  __skiller_if->write();
00536 
00537   if ( curss != "" ) {
00538     // We've got something to execute
00539 
00540 #ifdef SKILLER_TIMETRACKING
00541       __tt->ping_start(__ttc_luaprep);
00542 #endif
00543 
00544     // we're in continuous mode, reset status for this new loop
00545     if ( __continuous_run ) {
00546       // was continuous execution, status has to be cleaned up anyway
00547       //logger->log_debug("SkillerExecutionThread", "Resetting skill status in continuous mode");
00548       try {
00549         __lua->do_string("skillenv.reset_status()");
00550       } catch (Exception &e) {
00551         logger->log_warn("SkillerExecutionThread", "Caught exception while resetting status, ignored, output follows");
00552         logger->log_warn("SkillerExecutionThread", e);
00553       }
00554     }
00555 
00556     try {
00557       if (! __sksf_pushed) {
00558                                             // Stack:
00559         __lua->load_string(curss.c_str());  // sksf (skill string function)
00560         __lua->do_string("return skillenv.gensandbox()"); // sksf, sandbox
00561         __lua->setfenv();                   // sksf
00562         __sksf_pushed = true;
00563       }
00564 #ifdef SKILLER_TIMETRACKING
00565       __tt->ping_end(__ttc_luaprep);
00566       __tt->ping_start(__ttc_luaexec);
00567 #endif
00568       __lua->push_value(-1);              // sksf sksf
00569       __lua->pcall();                     // sksf
00570 
00571     } catch (Exception &e) {
00572       logger->log_error("SkillerExecutionThread", e);
00573       __skiller_if->set_error("Skill string execution failed with Lua error, see log");
00574       __skiller_if->write();
00575       __continuous_reset = true;
00576       __continuous_run   = false;
00577     }
00578 #ifdef SKILLER_TIMETRACKING
00579     __tt->ping_end(__ttc_luaexec);
00580 #endif
00581 
00582     if ( ! __continuous_run ) {
00583       // was one-shot execution, cleanup
00584       logger->log_debug("SkillerExecutionThread", "Resetting skills");
00585       try {
00586         if (__sksf_pushed) {
00587           __sksf_pushed = false;
00588           __lua->pop(1);                          // ---
00589         }
00590         __lua->do_string("skillenv.reset_all()");
00591       } catch (Exception &e) {
00592         logger->log_warn("SkillerExecutionThread", "Caught exception while resetting skills, ignored, output follows");
00593         logger->log_warn("SkillerExecutionThread", e);
00594       }
00595     }
00596 
00597   } // end if (curss != "")
00598 
00599 #ifdef SKILLER_TIMETRACKING
00600     __tt->ping_start(__ttc_publish);
00601 #endif
00602   publish_skill_status(curss);
00603   publish_skdbg();
00604   lua_loop_reset();
00605 
00606   __reader_just_left = false;
00607 
00608   __lua_ifi->write();
00609 #ifdef SKILLER_TIMETRACKING
00610   __tt->ping_end(__ttc_publish);
00611   __tt->ping_end(__ttc_total);
00612   if (++__tt_loopcount >= SKILLER_TT_MOD) {
00613     //logger->log_debug("Lua", "Stack size: %i", __lua->stack_size());
00614     __tt_loopcount = 0;
00615     __tt->print_to_stdout();
00616   }
00617 #endif
00618 }