]> git.pond.sub.org Git - empserver/blobdiff - src/server/update.c
Fix xdump updates not to dump bogus extra updates
[empserver] / src / server / update.c
index 22818fde6b2519e195fa9b21e73acd6974bcb4c6..dd6f2acb39c4e929b411edc5c26fdc08e7add4d3 100644 (file)
@@ -1,11 +1,11 @@
 /*
  *  Empire - A multi-player, client/server Internet based war game.
- *  Copyright (C) 1986-2000, Dave Pare, Jeff Bailey, Thomas Ruschak,
- *                           Ken Stevens, Steve McClure
+ *  Copyright (C) 1986-2011, Dave Pare, Jeff Bailey, Thomas Ruschak,
+ *                Ken Stevens, Steve McClure, Markus Armbruster
  *
- *  This program is free software; you can redistribute it and/or modify
+ *  Empire 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
+ *  the Free Software Foundation, either version 3 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  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
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  *  ---
  *
- *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
- *  related information and legal notices. It is expected that any future
- *  projects/authors will amend these files as needed.
+ *  See files README, COPYING and CREDITS in the root of the source
+ *  tree for related information and legal notices.  It is expected
+ *  that future projects/authors will amend these files as needed.
  *
  *  ---
  *
  *  update.c: Update scheduler
- * 
+ *
  *  Known contributors to this file:
  *     Dave Pare, 1994
  *     Steve McClure, 1996
+ *     Ron Koenderink, 2005
+ *     Markus Armbruster, 2007-2010
  */
 
+#include <config.h>
+
+#include <errno.h>
+#ifndef _WIN32
+#include <sys/wait.h>
+#endif
+#include <time.h>
+#include "empthread.h"
+#include "file.h"
+#include "game.h"
 #include "misc.h"
+#include "optlist.h"
 #include "player.h"
-#include "keyword.h"
-#include "empthread.h"
 #include "prototypes.h"
-#include "optlist.h"
+#include "server.h"
 
-empth_sem_t    *update_sem;
+/*
+ * Update is running.
+ * Can be used to suppress messages, or direct them to bulletins.
+ */
+int update_running;
 
-extern void update_main();
-extern void update_wait();
-time_t  update_time;
+static time_t update_schedule_anchor;
+static int update_wanted;
+
+static empth_t *update_thread;
+
+static int update_get_schedule(void);
+static void update_sched(void *);
+static void update_run(void);
 
-/*ARGSUSED*/
 void
-update_sched(argv)
-void   *argv;
+update_init(void)
 {
-       extern  int s_p_etu;
-       extern  int etu_per_update;
-       extern  int adj_update;
-       extern  int update_window;
-       s_char  *kw;
-       int     hour[2];
-       int     wind;
-       time_t  now, delta;
-
-       update_sem = empth_sem_create("Update", 0);
-       empth_create(PP_SCHED, update_wait, (50*1024), 0, "UpdateWait",
-               "Waits until players idle", 0);
-       time(&now);
-       (void) gamehours(now, hour);
-       if (NULL != (kw = kw_find("s_p_etu")))
-               kw_parse(CF_VALUE, kw, &s_p_etu);
-       if (NULL != (kw = kw_find("etu_per_update")))
-               kw_parse(CF_VALUE, kw, &etu_per_update);
-       if (NULL != (kw = kw_find("adj_update")))
-               kw_parse(CF_VALUE, kw, &adj_update);
-       if (NULL != (kw = kw_find("update_window")))
-               kw_parse(CF_VALUE, kw, &update_window);
-       if (s_p_etu <= 0) {
-               logerror("bad value for s_p_etu (%d)", s_p_etu);
-               s_p_etu = 2 * 60;
-               logerror("setting s_p_etu to %d", s_p_etu);
+    struct player *dp;
+    int stacksize;
+
+    update_schedule_anchor = (time(NULL) + 59) / 60 * 60;
+    if (update_get_schedule() < 0)
+       exit(1);
+
+    play_lock = empth_rwlock_create("Update");
+    if (!play_lock)
+       exit_nomem();
+
+    dp = player_new(-1);
+    if (!dp)
+       exit_nomem();
+    /* FIXME ancient black magic; figure out true stack need */
+    stacksize = 100000 +
+/* finish_sects */ WORLD_X * WORLD_Y * (2 * sizeof(double) +
+                                       sizeof(char *));
+    update_thread = empth_create(update_sched, stacksize, 0, "Update", dp);
+    if (!update_thread)
+       exit_nomem();
+}
+
+/*
+ * Get the schedule for future updates into update_time[].
+ * Return 0 on success, -1 on failure.
+ */
+static int
+update_get_schedule(void)
+{
+    time_t now = time(NULL);
+    int n = sizeof(update_time) / sizeof(*update_time);
+    int i;
+
+    ef_truncate(EF_UPDATES, 0);
+    ef_extend(EF_UPDATES, n - 1);
+    if (read_schedule(schedulefil, update_time, n,
+                     now + 30, update_schedule_anchor) < 0) {
+       logerror("No update schedule!");
+       ef_truncate(EF_UPDATES, 0);
+       return -1;
+    }
+    logerror("Update schedule read");
+    for (i = 0; update_time[i]; i++) ;
+    ef_truncate(EF_UPDATES, i);
+    return 0;
+}
+
+/*ARGSUSED*/
+static void
+update_sched(void *unused)
+{
+    time_t next_update, now;
+
+    player->proc = empth_self();
+    player->cnum = 0;
+    player->god = 1;
+
+    for (;;) {
+       /*
+        * Sleep until the next scheduled update or an unscheduled
+        * wakeup.
+        */
+       next_update = update_time[0];
+       if (next_update) {
+           if (update_window > 0)
+               next_update += random() % update_window;
+           logerror("Next update at %s", ctime(&next_update));
+           /* sleep until update is scheduled to go off */
+           empth_sleep(next_update);
+       } else {
+           logerror("No update scheduled");
+           /* want to sleep forever, but empthread doesn't provide that */
+           while (empth_sleep(time(NULL) + (60 * 60 * 24)) >= 0) ;
+       }
+
+       now = time(NULL);
+       if (next_update != 0 && now >= next_update) {
+           /* scheduled update time reached */
+           if (now >= next_update + 60)
+               logerror("Missed the update!");
+           else if (update_demand == UPD_DEMAND_SCHED && !demand_check())
+               ;
+           else if (updates_disabled())
+               logerror("Updates disabled...skipping update");
+           else
+               update_wanted = 1;
+           update_schedule_anchor = update_time[0];
        }
-       while (1) {
-               time(&now);
-               next_update_time(&now, &update_time, &delta);
-               if (update_window > 0) {
-                       wind = (random() % update_window);
-                       update_time += wind;
-                       delta += wind;
-               }
-               logerror("Next update at %s", ctime(&update_time));
-               logerror("Next update in %d seconds", delta);
-               /* sleep until update is scheduled to go off */
-               empth_sleep(update_time);
-               time(&now);
-               now += adj_update;
-               if (!gamehours(now, hour)) {
-                       logerror("No update permitted (hours restriction)");
-                       continue;
-               }
-               if (!updatetime(&now)) {
-                       logerror("No update wanted");
-                       continue;
-               }
-               if (updates_disabled()) {
-                       logerror("Updates disabled...skipping update");
-                       continue;
-               }
-               empth_sem_signal(update_sem);
+       /* else unscheduled update if update_wanted is set */
+
+       if (update_wanted) {
+           update_wanted = 0;
+           update_run();
        }
-       /*NOTREACHED*/
+
+       update_get_schedule();
+    }
+    /*NOTREACHED*/
 }
 
-/*ARGSUSED*/
-void
-update_wait(argv)
-       void    *argv;
+/*
+ * Trigger an update.
+ * Return 0 on success, -1 on failure.
+ */
+int
+update_trigger(void)
+{
+    logerror("Triggering unscheduled update");
+    update_wanted = 1;
+    empth_wakeup(update_thread);
+    return 0;
+}
+
+/*
+ * Reload the update schedule.
+ * Return 0 on success, -1 on failure.
+ */
+int
+update_reschedule(void)
+{
+    empth_wakeup(update_thread);
+    return 0;
+}
+
+static void
+update_run(void)
 {
-       struct  player *p;
-       int     running;
-       time_t  now;
-       int     stacksize;
-       struct  player *dp;
-
-       while (1) {
-               empth_sem_wait(update_sem);
-               running = 0;
-               for (p = player_next(0); p != 0; p = player_next(p)) {
-                       if (p->state != PS_PLAYING)
-                               continue;
-                       if (p->command) {
-                               pr_flash(p, "Update aborting command\n");
-                               p->aborted = 1;
-                               empth_wakeup(p->proc);
-                               running++;
-                       }
-               }
-               time(&now);
-               if (running) {
-                       /* sleep a few, wait for aborts to take effect */
-                       empth_sleep(now + 2);
-               }
-               /* 
-                * we rely on the fact that update's priority is the highest
-                * in the land so it can finish before it yields.
-                */
-               dp = player_new(0, 0);
-               stacksize= 100000 +
-/* finish_sects */        WORLD_X*WORLD_Y*(2*sizeof(double)+sizeof(s_char *));
-                       
-               empth_create(PP_UPDATE, update_main, stacksize, 0,
-                       "UpdateRun", "Updates the world", dp);
+    struct player *p;
+
+    for (p = player_next(NULL); p; p = player_next(p)) {
+       if (p->state != PS_PLAYING)
+           continue;
+       if (p->command) {
+           pr_flash(p, "Update aborting command\n");
+           p->may_sleep = PLAYER_SLEEP_NEVER;
+           p->aborted = 1;
+           empth_wakeup(p->proc);
        }
-       /*NOTREACHED*/
+    }
+    empth_rwlock_wrlock(play_lock);
+    if (*pre_update_hook) {
+       if (run_hook(pre_update_hook, "pre-update")) {
+           empth_rwlock_unlock(play_lock);
+           return;
+       }
+    }
+    update_running = 1;
+    update_main();
+    update_running = 0;
+    empth_rwlock_unlock(play_lock);
+}
+
+int
+run_hook(char *cmd, char *name)
+{
+    int status;
+
+    fflush(NULL);
+
+    status = system(cmd);
+    if (status == 0)
+       ;                       /* successful exit */
+    else if (status == -1)
+       logerror("couldn't execute command processor for %s hook (%s)",
+                name, strerror(errno));
+#ifndef _WIN32
+    else if (WIFEXITED(status))
+       logerror("%s hook terminated unsuccessfully (exit status %d)",
+                name, WEXITSTATUS(status));
+    else if (WIFSIGNALED(status))
+       logerror("%s hook terminated abnormally (signal %d)",
+                name, WTERMSIG(status));
+#endif
+    else if (status)
+       logerror("%s hook terminated strangely (status %d)",
+                name, status);
+    return status;
 }