| @@ -35,6 +35,7 @@ DebugSprite::DebugSprite(Game *game) | |||
| { | |||
| data = new DebugSpriteData(); | |||
| data->game = game; | |||
| Ticker::Ref(game); | |||
| data->tiler = Tiler::Register("art/test/character-dress.png"); | |||
| data->x = 320; | |||
| data->y = 206; | |||
| @@ -71,6 +72,7 @@ void DebugSprite::TickDraw(float deltams) | |||
| DebugSprite::~DebugSprite() | |||
| { | |||
| Ticker::Unref(data->game); | |||
| Tiler::Deregister(data->tiler); | |||
| delete data; | |||
| } | |||
| @@ -85,7 +85,7 @@ int Dict::MakeSlot(char const *name) | |||
| } | |||
| else | |||
| { | |||
| data->entities[id]->Ref(); | |||
| Ticker::Ref(data->entities[id]); | |||
| } | |||
| return id; | |||
| @@ -93,14 +93,14 @@ int Dict::MakeSlot(char const *name) | |||
| void Dict::RemoveSlot(int id) | |||
| { | |||
| if (data->entities[id]->Unref() == 0) | |||
| if (Ticker::Unref(data->entities[id]) == 0) | |||
| data->entities[id] = NULL; | |||
| } | |||
| void Dict::SetEntity(int id, Entity *entity) | |||
| { | |||
| entity->Ref(); | |||
| Ticker::Ref(entity); | |||
| data->entities[id] = entity; | |||
| } | |||
| @@ -63,13 +63,3 @@ void Entity::TickDraw(float deltams) | |||
| #endif | |||
| } | |||
| void Entity::Ref() | |||
| { | |||
| ref++; | |||
| } | |||
| int Entity::Unref() | |||
| { | |||
| return --ref; | |||
| } | |||
| @@ -22,10 +22,6 @@ class Entity | |||
| friend class TickerData; | |||
| friend class Dict; | |||
| public: | |||
| virtual void Ref(); | |||
| virtual int Unref(); | |||
| protected: | |||
| typedef enum | |||
| { | |||
| @@ -47,8 +43,8 @@ protected: | |||
| virtual void TickGame(float deltams); | |||
| virtual void TickDraw(float deltams); | |||
| Entity *next; | |||
| int ref, destroy; | |||
| Entity *next, *autonext; | |||
| int ref, autorelease, destroy; | |||
| #if !FINAL_RELEASE | |||
| enum | |||
| @@ -74,7 +74,14 @@ void Font::TickDraw(float deltams) | |||
| { | |||
| Entity::TickDraw(deltams); | |||
| if (data->img) | |||
| if (destroy) | |||
| { | |||
| if (data->img) | |||
| SDL_FreeSurface(data->img); | |||
| else | |||
| glDeleteTextures(1, &data->texture); | |||
| } | |||
| else if (data->img) | |||
| { | |||
| data->width = data->img->w / 16; | |||
| data->height = data->img->h / 16; | |||
| @@ -91,11 +98,6 @@ void Font::TickDraw(float deltams) | |||
| SDL_FreeSurface(data->img); | |||
| data->img = NULL; | |||
| } | |||
| else if (ref == 0) | |||
| { | |||
| glDeleteTextures(1, &data->texture); | |||
| destroy = 1; | |||
| } | |||
| } | |||
| char const *Font::GetName() | |||
| @@ -16,6 +16,15 @@ | |||
| #include "glmapview.h" | |||
| #include "debugfps.h" | |||
| static gboolean delayed_quit(GtkWidget *w, GdkEvent *e, void *data) | |||
| { | |||
| (void)w; | |||
| (void)e; | |||
| (void)data; | |||
| gtk_main_quit(); | |||
| return TRUE; | |||
| } | |||
| int main(int argc, char **argv) | |||
| { | |||
| /* Initialize GTK */ | |||
| @@ -41,12 +50,14 @@ int main(int argc, char **argv) | |||
| GlMapView *glmapview = new GlMapView(builder); | |||
| /* Show window. We're good to go! */ | |||
| gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(builder, "window"))); | |||
| GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); | |||
| gtk_widget_show_all(window); | |||
| gtk_signal_connect(GTK_OBJECT(window), "delete_event", | |||
| GTK_SIGNAL_FUNC(delayed_quit), NULL); | |||
| g_object_unref(G_OBJECT(builder)); | |||
| new DebugFps(); | |||
| glmapview->LoadMap("maps/testmap.tmx"); | |||
| glmapview->SetFocus(); | |||
| new DebugFps(); | |||
| gtk_main(); | |||
| @@ -4,7 +4,6 @@ | |||
| <!-- interface-naming-policy project-wide --> | |||
| <object class="GtkWindow" id="window"> | |||
| <property name="title" translatable="yes">Deus Hax Editor</property> | |||
| <signal name="delete_event" handler="gtk_main_quit"/> | |||
| <child> | |||
| <object class="GtkVBox" id="vbox1"> | |||
| <property name="visible">True</property> | |||
| @@ -23,8 +22,8 @@ | |||
| <child> | |||
| <object class="GtkImageMenuItem" id="imagemenuitem1"> | |||
| <property name="visible">True</property> | |||
| <property name="related_action">action_new</property> | |||
| <property name="use_action_appearance">True</property> | |||
| <property name="related_action">action_new</property> | |||
| <property name="use_underline">True</property> | |||
| <property name="use_stock">True</property> | |||
| </object> | |||
| @@ -32,8 +31,8 @@ | |||
| <child> | |||
| <object class="GtkImageMenuItem" id="imagemenuitem2"> | |||
| <property name="visible">True</property> | |||
| <property name="related_action">action_open</property> | |||
| <property name="use_action_appearance">True</property> | |||
| <property name="related_action">action_open</property> | |||
| <property name="use_underline">True</property> | |||
| <property name="use_stock">True</property> | |||
| </object> | |||
| @@ -67,6 +66,7 @@ | |||
| <property name="use_stock">True</property> | |||
| <property name="always_show_image">True</property> | |||
| <signal name="activate" handler="gtk_main_quit"/> | |||
| <signal name="activate" handler="gtk_true"/> | |||
| </object> | |||
| </child> | |||
| </object> | |||
| @@ -158,8 +158,8 @@ | |||
| <child> | |||
| <object class="GtkToolButton" id="toolbutton2"> | |||
| <property name="visible">True</property> | |||
| <property name="related_action">action_new</property> | |||
| <property name="use_action_appearance">True</property> | |||
| <property name="related_action">action_new</property> | |||
| <property name="label" translatable="yes">toolbutton</property> | |||
| <property name="use_underline">True</property> | |||
| </object> | |||
| @@ -171,8 +171,8 @@ | |||
| <child> | |||
| <object class="GtkToolButton" id="toolbutton1"> | |||
| <property name="visible">True</property> | |||
| <property name="related_action">action_open</property> | |||
| <property name="use_action_appearance">True</property> | |||
| <property name="related_action">action_open</property> | |||
| <property name="label" translatable="yes">Open...</property> | |||
| <property name="use_underline">True</property> | |||
| </object> | |||
| @@ -184,8 +184,8 @@ | |||
| <child> | |||
| <object class="GtkToolButton" id="toolbutton4"> | |||
| <property name="visible">True</property> | |||
| <property name="related_action">action_save</property> | |||
| <property name="use_action_appearance">True</property> | |||
| <property name="related_action">action_save</property> | |||
| <property name="label" translatable="yes">Save</property> | |||
| <property name="use_underline">True</property> | |||
| </object> | |||
| @@ -50,11 +50,10 @@ GlMapView::GlMapView(GtkBuilder *builder) | |||
| * stealing time from the GTK loop when the callback time exceeds | |||
| * the timeout value. */ | |||
| g_idle_add((GSourceFunc)IdleTickSignal, this); | |||
| gtk_quit_add(0, (GtkFunction)ShutdownSignal, this); | |||
| gtk_signal_connect(GTK_OBJECT(glarea), "realize", | |||
| GTK_SIGNAL_FUNC(SetupSignal), this); | |||
| gtk_signal_connect(GTK_OBJECT(glarea), "destroy", | |||
| GTK_SIGNAL_FUNC(DestroySignal), this); | |||
| gtk_signal_connect(GTK_OBJECT(glarea), "expose_event", | |||
| GTK_SIGNAL_FUNC(DrawSignal), this); | |||
| gtk_signal_connect(GTK_OBJECT(glarea), "configure_event", | |||
| @@ -75,17 +74,28 @@ void GlMapView::LoadMap(char const *path) | |||
| { | |||
| // FIXME: detect when the map viewer is killed | |||
| mapviewer = new MapViewer(path); | |||
| Ticker::Ref(mapviewer); | |||
| UpdateAdjustments(); | |||
| } | |||
| void GlMapView::SetFocus() | |||
| void GlMapView::CloseMap() | |||
| { | |||
| gtk_widget_grab_focus(glarea); | |||
| if (mapviewer) | |||
| Ticker::Unref(mapviewer); | |||
| mapviewer = NULL; | |||
| UpdateAdjustments(); | |||
| } | |||
| gboolean GlMapView::IdleTick() | |||
| { | |||
| if (Ticker::Finished()) | |||
| { | |||
| gtk_main_quit(); | |||
| return FALSE; | |||
| } | |||
| // FIXME: do not do anything if the previous tick was too recent? | |||
| ticking = TRUE; | |||
| @@ -104,6 +114,7 @@ gboolean GlMapView::IdleTick() | |||
| gboolean GlMapView::Setup() | |||
| { | |||
| /* Set up display */ | |||
| gtk_widget_grab_focus(glarea); | |||
| if (gtk_gl_area_make_current(GTK_GL_AREA(glarea))) | |||
| Video::Setup(glarea->allocation.width, glarea->allocation.height); | |||
| @@ -112,9 +123,13 @@ gboolean GlMapView::Setup() | |||
| return TRUE; | |||
| } | |||
| gboolean GlMapView::Destroy() | |||
| gboolean GlMapView::Shutdown() | |||
| { | |||
| g_idle_remove_by_data(this); | |||
| CloseMap(); | |||
| Ticker::Shutdown(); | |||
| /* Hijack the exit process by adding another level of gtk_main */ | |||
| gtk_widget_set_sensitive(gtk_widget_get_toplevel(glarea), FALSE); | |||
| gtk_main(); | |||
| return TRUE; | |||
| } | |||
| @@ -239,10 +254,9 @@ gboolean GlMapView::SetupSignal(GtkWidget *w, GlMapView *that) | |||
| return that->Setup(); | |||
| } | |||
| gboolean GlMapView::DestroySignal(GtkWidget *w, GlMapView *that) | |||
| gboolean GlMapView::ShutdownSignal(GlMapView *that) | |||
| { | |||
| (void)w; | |||
| return that->Destroy(); | |||
| return that->Shutdown(); | |||
| } | |||
| gboolean GlMapView::DrawSignal(GtkWidget *w, GdkEventExpose *e, | |||
| @@ -13,13 +13,13 @@ class GlMapView | |||
| public: | |||
| GlMapView(GtkBuilder *builder); | |||
| void LoadMap(char const *path); | |||
| void SetFocus(); | |||
| void CloseMap(); | |||
| private: | |||
| /* Private methods */ | |||
| gboolean IdleTick(); | |||
| gboolean Setup(); | |||
| gboolean Destroy(); | |||
| gboolean Shutdown(); | |||
| gboolean Draw(GdkEventExpose *e); | |||
| void Scroll(double dx, double dy); | |||
| void UpdateAdjustments(); | |||
| @@ -30,7 +30,7 @@ private: | |||
| /* Private signal slots */ | |||
| static gboolean IdleTickSignal(GlMapView *that); | |||
| static gboolean SetupSignal(GtkWidget *w, GlMapView *that); | |||
| static gboolean DestroySignal(GtkWidget *w, GlMapView *that); | |||
| static gboolean ShutdownSignal(GlMapView *that); | |||
| static gboolean DrawSignal(GtkWidget *w, GdkEventExpose *e, | |||
| GlMapView *that); | |||
| static gboolean ReshapeSignal(GtkWidget *w, GdkEventConfigure *e, | |||
| @@ -21,7 +21,6 @@ class SdlInputData | |||
| friend class SdlInput; | |||
| private: | |||
| Game *game; | |||
| int mx, my; | |||
| }; | |||
| @@ -29,12 +28,11 @@ private: | |||
| * Public SdlInput class | |||
| */ | |||
| SdlInput::SdlInput(Game *game) | |||
| SdlInput::SdlInput() | |||
| { | |||
| SDL_Init(SDL_INIT_TIMER); | |||
| data = new SdlInputData(); | |||
| data->game = game; | |||
| SDL_GetMouseState(&data->mx, &data->my); | |||
| } | |||
| @@ -47,9 +45,6 @@ void SdlInput::TickGame(float deltams) | |||
| { | |||
| Entity::TickGame(deltams); | |||
| if (data->game->Finished()) | |||
| destroy = 1; | |||
| /* Handle mouse input */ | |||
| SDL_GetMouseState(&data->mx, &data->my); | |||
| @@ -58,7 +53,7 @@ void SdlInput::TickGame(float deltams) | |||
| while (SDL_PollEvent(&event)) | |||
| { | |||
| if (event.type == SDL_QUIT) | |||
| data->game->Quit(); | |||
| Ticker::Shutdown(); | |||
| #if 0 | |||
| else if (event.type == SDL_KEYDOWN) | |||
| Input::KeyPressed(event.key.keysym.sym, deltams); | |||
| @@ -19,7 +19,7 @@ class SdlInputData; | |||
| class SdlInput : public Entity | |||
| { | |||
| public: | |||
| SdlInput(Game *game); | |||
| SdlInput(); | |||
| virtual ~SdlInput(); | |||
| protected: | |||
| @@ -48,12 +48,12 @@ int main(int argc, char **argv) | |||
| Game *game = new Game("maps/testmap.tmx"); | |||
| /* Register an input driver and some debug stuff */ | |||
| new SdlInput(game); | |||
| new SdlInput(); | |||
| new DebugFps(); | |||
| new DebugSprite(game); | |||
| //new DebugRecord("lolengine.ogg"); | |||
| while (!game->Finished()) | |||
| while (!Ticker::Finished()) | |||
| { | |||
| /* Tick the game */ | |||
| Ticker::TickGame(); | |||
| @@ -23,7 +23,8 @@ static class TickerData | |||
| public: | |||
| TickerData() : | |||
| todo(0), nentities(0), | |||
| todolist(0), autolist(0), | |||
| nentities(0), | |||
| frame(0), deltams(0), bias(0) | |||
| { | |||
| for (int i = 0; i < Entity::GROUP_COUNT; i++) | |||
| @@ -35,17 +36,26 @@ public: | |||
| #if !FINAL_RELEASE | |||
| if (nentities) | |||
| fprintf(stderr, "ERROR: still %i entities in ticker\n", nentities); | |||
| if (autolist) | |||
| { | |||
| int count = 0; | |||
| for (Entity *e = autolist; e; e = e->autonext, count++) | |||
| ; | |||
| fprintf(stderr, "ERROR: still %i autoreleased entities\n", count); | |||
| } | |||
| fprintf(stderr, "INFO: %i frames required to quit\n", | |||
| frame - quitframe); | |||
| #endif | |||
| } | |||
| private: | |||
| /* Entity management */ | |||
| Entity *todo; | |||
| Entity *todolist, *autolist; | |||
| Entity *list[Entity::GROUP_COUNT]; | |||
| int nentities; | |||
| /* Fixed framerate management */ | |||
| int frame; | |||
| int frame, quitframe; | |||
| Timer timer; | |||
| float deltams, bias; | |||
| } | |||
| @@ -62,8 +72,55 @@ void Ticker::Register(Entity *entity) | |||
| /* If we are called from its constructor, the object's vtable is not | |||
| * ready yet, so we do not know which group this entity belongs to. Wait | |||
| * until the first tick. */ | |||
| entity->next = data->todo; | |||
| data->todo = entity; | |||
| entity->next = data->todolist; | |||
| data->todolist = entity; | |||
| /* Objects are autoreleased by default. Put them in a circular list. */ | |||
| entity->autorelease = 1; | |||
| entity->autonext = data->autolist; | |||
| data->autolist = entity; | |||
| entity->ref = 1; | |||
| data->nentities++; | |||
| } | |||
| void Ticker::Ref(Entity *entity) | |||
| { | |||
| #if !FINAL_RELEASE | |||
| if (entity->destroy) | |||
| fprintf(stderr, "ERROR: refing entity scheduled for destruction\n"); | |||
| #endif | |||
| if (entity->autorelease) | |||
| { | |||
| /* Get the entity out of the autorelease list. This is usually | |||
| * very fast since the first entry in autolist is the last | |||
| * registered entity. */ | |||
| for (Entity *e = data->autolist, *prev = NULL; e; | |||
| prev = e, e = e->autonext) | |||
| { | |||
| if (e == entity) | |||
| { | |||
| if (prev) | |||
| prev->autonext = e->autonext; | |||
| else | |||
| data->autolist = e->autonext; | |||
| break; | |||
| } | |||
| } | |||
| entity->autorelease = 0; | |||
| } | |||
| else | |||
| entity->ref++; | |||
| } | |||
| int Ticker::Unref(Entity *entity) | |||
| { | |||
| #if !FINAL_RELEASE | |||
| if (entity->ref <= 0) | |||
| fprintf(stderr, "ERROR: dereferencing unreferenced entity\n"); | |||
| if (entity->autorelease) | |||
| fprintf(stderr, "ERROR: dereferencing autoreleased entity\n"); | |||
| #endif | |||
| return --entity->ref; | |||
| } | |||
| void Ticker::TickGame() | |||
| @@ -73,6 +130,17 @@ void Ticker::TickGame() | |||
| Profiler::Start(Profiler::STAT_TICK_GAME); | |||
| #if 0 | |||
| fprintf(stderr, "-------------------------------------\n"); | |||
| for (int i = 0; i < Entity::GROUP_COUNT; i++) | |||
| { | |||
| fprintf(stderr, "Group %i\n", i); | |||
| for (Entity *e = data->list[i]; e; e = e->next) | |||
| fprintf(stderr, " \\-- %s (ref %i, destroy %i)\n", e->GetName(), e->ref, e->destroy); | |||
| } | |||
| #endif | |||
| data->frame++; | |||
| data->deltams = data->timer.GetMs(); | |||
| @@ -82,7 +150,8 @@ void Ticker::TickGame() | |||
| * before inserting awaiting objects, because there is no way these | |||
| * are already marked for destruction. */ | |||
| for (int i = 0; i < Entity::GROUP_COUNT; i++) | |||
| for (Entity *e = data->list[i], *prev = NULL; e; prev = e, e = e->next) | |||
| for (Entity *e = data->list[i], *prev = NULL; e; ) | |||
| { | |||
| if (e->destroy) | |||
| { | |||
| if (prev) | |||
| @@ -90,20 +159,30 @@ void Ticker::TickGame() | |||
| else | |||
| data->list[i] = e->next; | |||
| Entity *tmp = e; | |||
| e = e->next; | |||
| delete tmp; | |||
| data->nentities--; | |||
| delete e; | |||
| } | |||
| else | |||
| { | |||
| if (e->ref <= 0) | |||
| e->destroy = 1; | |||
| prev = e; | |||
| e = e->next; | |||
| } | |||
| } | |||
| /* Insert waiting objects into the appropriate lists */ | |||
| while (data->todo) | |||
| while (data->todolist) | |||
| { | |||
| Entity *e = data->todo; | |||
| data->todo = e->next; | |||
| Entity *e = data->todolist; | |||
| data->todolist = e->next; | |||
| int i = e->GetGroup(); | |||
| e->next = data->list[i]; | |||
| data->list[i] = e; | |||
| data->nentities++; | |||
| } | |||
| /* Tick objects for the game loop */ | |||
| @@ -169,3 +248,20 @@ int Ticker::GetFrameNum() | |||
| return data->frame; | |||
| } | |||
| void Ticker::Shutdown() | |||
| { | |||
| /* We're bailing out. Release all autorelease objects. */ | |||
| while (data->autolist) | |||
| { | |||
| data->autolist->ref--; | |||
| data->autolist = data->autolist->autonext; | |||
| } | |||
| data->quitframe = data->frame; | |||
| } | |||
| int Ticker::Finished() | |||
| { | |||
| return !data->nentities; | |||
| } | |||
| @@ -20,11 +20,16 @@ class Ticker | |||
| { | |||
| public: | |||
| static void Register(Entity *entity); | |||
| static void Ref(Entity *entity); | |||
| static int Unref(Entity *entity); | |||
| static void TickGame(); | |||
| static void TickDraw(); | |||
| static void ClampFps(float deltams); | |||
| static int GetFrameNum(); | |||
| static void Shutdown(); | |||
| static int Finished(); | |||
| }; | |||
| #endif // __DH_TICKER_H__ | |||
| @@ -83,7 +83,14 @@ void TileSet::TickDraw(float deltams) | |||
| { | |||
| Entity::TickDraw(deltams); | |||
| if (data->img) | |||
| if (destroy) | |||
| { | |||
| if (data->img) | |||
| SDL_FreeSurface(data->img); | |||
| else | |||
| glDeleteTextures(1, &data->texture); | |||
| } | |||
| else if (data->img) | |||
| { | |||
| glGenTextures(1, &data->texture); | |||
| glBindTexture(GL_TEXTURE_2D, data->texture); | |||
| @@ -97,11 +104,6 @@ void TileSet::TickDraw(float deltams) | |||
| SDL_FreeSurface(data->img); | |||
| data->img = NULL; | |||
| } | |||
| else if (ref == 0) | |||
| { | |||
| glDeleteTextures(1, &data->texture); | |||
| destroy = 1; | |||
| } | |||
| } | |||
| char const *TileSet::GetName() | |||