@@ -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() | |||