namespace app {
-Application::Application(sdl::InitScreen *screen, State *initialState)
+Application::Application(sdl::InitScreen &screen, State *initialState)
: screen(screen)
, states()
, last(SDL_GetTicks())
, inStateChage(false) {
- assert(screen && "cannot create application without screen");
assert(initialState && "cannot create application without initial state");
RealPushState(initialState);
}
void Application::RealChangeState(State *s) {
if (!states.empty()) {
- states.top()->PauseState(*this, screen->Screen());
- states.top()->ExitState(*this, screen->Screen());
+ states.top()->PauseState(*this, screen.Screen());
+ states.top()->ExitState(*this, screen.Screen());
states.pop();
}
states.push(s);
- s->EnterState(*this, screen->Screen());
- s->ResumeState(*this, screen->Screen());
+ s->EnterState(*this, screen.Screen());
+ s->ResumeState(*this, screen.Screen());
}
void Application::RealPushState(State *s) {
if (!states.empty()) {
- states.top()->PauseState(*this, screen->Screen());
+ states.top()->PauseState(*this, screen.Screen());
}
states.push(s);
- s->EnterState(*this, screen->Screen());
- s->ResumeState(*this, screen->Screen());
+ s->EnterState(*this, screen.Screen());
+ s->ResumeState(*this, screen.Screen());
}
void Application::RealPopState() {
if (states.empty()) return;
- states.top()->PauseState(*this, screen->Screen());
- states.top()->ExitState(*this, screen->Screen());
+ states.top()->PauseState(*this, screen.Screen());
+ states.top()->ExitState(*this, screen.Screen());
delete states.top();
states.pop();
if (!states.empty()) {
- states.top()->ResumeState(*this, screen->Screen());
+ states.top()->ResumeState(*this, screen.Screen());
}
}
void Application::PopAllStates() {
while (!states.empty()) {
- states.top()->PauseState(*this, screen->Screen());
- states.top()->ExitState(*this, screen->Screen());
+ states.top()->PauseState(*this, screen.Screen());
+ states.top()->ExitState(*this, screen.Screen());
delete states.top();
states.pop();
}
PopAllStates();
break;
case SDL_VIDEORESIZE:
- screen->Resize(event.resize.w, event.resize.h);
+ screen.Resize(event.resize.w, event.resize.h);
CurrentState()->Resize(event.resize.w, event.resize.h);
break;
case SDL_KEYDOWN:
void Application::Render(void) {
if (!CurrentState()) return;
- CurrentState()->Render(screen->Screen());
- screen->Flip();
+ CurrentState()->Render(screen.Screen());
+ screen.Flip();
}
}
namespace app {
+/// Application controller class.
+/// Operates on a state stack that can be modified via ChangeState, PushState,
+/// and PopState.
+/// All state changes are delayed until the looping mechanism gets control again
+/// (i.e. after a top level state function returns, not during).
+/// SDL key events are preprocessed, see app::Input.
+/// The quit event (typically window closed or signal received) is caught and
+/// results in immediate (that is, after the next input loop) termination.
+/// Popped states will be deleted via the plain delete operator on an app::State
+/// pointer.
+/// Timers created by GlobalTimers() operate on actual application time and are
+/// not paused when the current state is paused (as are the timers started by
+/// the app::State members).
class Application {
public:
- Application(sdl::InitScreen *screen, State *initialState);
+ Application(sdl::InitScreen &screen, State *initialState);
~Application();
private:
Application(const Application &);
void Render();
private:
- sdl::InitScreen *screen;
+ sdl::InitScreen &screen;
std::stack<State *> states;
std::queue<StateCommand> stateChanges;
Timers<Uint32> globalTimers;
namespace app {
+/// Specialized argument interpreter.
+/// Reads command line arguments via Read(int, char**) function.
class Arguments {
public:
namespace app {
+/// Maps SDL key events to virtual buttons.
+/// Records the state and whether it was pressed/released in the current
+/// iteration.
+/// Multiple buttons can be passed by ORing them bitwise.
+/// The MapKey(SDLKey, Button) function introduces a mapping for the given key
+/// to the given virtual button. Each key can be assigned to one button only,
+/// but there is no limit (well, except for memory, I guess) on how many keys
+/// may map to the same button.
+/// Each iteration should first call ResetInteractiveState() to drop the just
+/// pressed/released information and then pass each SDL_KeyboardEvent to
+/// HandleKeyboardEvent(const SDL_KeyboardEvent &).
+/// The four DEBUG_? buttons do not map to any real SNES/Lufia 2 button and may
+/// be used for debugging or non-gameplay-related input.
class Input {
public:
namespace app {
+/// Application state base class for use with app::Application.
class State {
public:
virtual ~State() { };
public:
- /// do some setup
- /// called when the state first enters the stack
+ /// Do some setup that needs an application and/or screen handle and thus
+ /// can not be done by the constructor.
+ /// Called when the state first enters the stack.
/// @param ctrl the Application running the state
virtual void EnterState(Application &ctrl, SDL_Surface *screen) = 0;
- /// do some cleanup
- /// called when the state is popped from the stack
+ /// Do some cleanup.
+ /// Called when the state is popped from the stack.
virtual void ExitState(Application &ctrl, SDL_Surface *screen) = 0;
- /// called when the state becomes the active one
+ /// Called when the state becomes the active one.
virtual void ResumeState(Application &ctrl, SDL_Surface *screen) = 0;
- /// called when the state becomes inactive
+ /// Called when the state becomes inactive.
virtual void PauseState(Application &ctrl, SDL_Surface *screen) = 0;
- /// adapt the state's graphics to given dimensions
+ /// Adapt the state's graphics to given dimensions.
+ /// NOTE: currently, this is only called for the stack top and not
+ /// propagated on stack changes.
+ /// Will be fixed soom ;).
virtual void Resize(int width, int height) = 0;
+ /// Handle interactive events such as input and timers.
virtual void HandleEvents(const Input &) = 0;
+ /// Update the time-dependant world representation.
virtual void UpdateWorld(float deltaT) = 0;
+ /// Draw a picture of the world.
virtual void Render(SDL_Surface *) = 0;
public:
+ /// Timers handle intended for graphics, sync'ed with render time.
+ /// These timers are only updated for the stack top and thus appear paused
+ /// when the state is visible (roughly).
Timers<Uint32> &GraphicsTimers() { return graphicsTimers; }
+ /// Timers handle intended for graphics, sync'ed with world time.
+ /// These timers are only updated for the stack top and thus appear paused
+ /// when the state is visible (roughly).
Timers<float> &PhysicsTimers() { return physicsTimers; }
private:
namespace app {
+/// Stores timing information.
+/// For use by app::Timer.
template<class Time>
struct TimerData {
};
+/// Timer handle.
+/// How the various information returned by the const member functions is to be
+/// interpreted highly depends on how the timer was created (by app::Timers).
template<class Time>
class Timer {
}
public:
+ /// Check if the timer was started (and not cleared) yet.
bool Started() const {
return data;
}
+ /// Check if the timer has reached its target time (only sensible for
+ /// countdown timers).
bool Finished() const {
return data && data->target != Time() && !data->repeat && data->time >= data->target;
}
+ /// Check if the timer was started and has not finished yet (in case it's a
+ /// countdown).
bool Running() const {
return data && !Finished();
}
+ /// Get the elapsed time since the timer started.
Time Elapsed() const {
return data ? data->time : Time();
}
+ /// Get the time remaining for countdowns.
Time Remaining() const {
return data ? (data->target - data->time) : Time();
}
+ /// Get the iteration index for interval timers.
int Iteration() const {
return (data && data->target > Time()) ? std::floor(data->time / data->target) : Time();
}
+ /// Check if the timer reached its interval or countdown goal this iteration.
bool JustHit() const {
return data && data->justHit;
}
+ /// Unset the timer (does not stop other handles for a shared timer).
void Clear() {
if (data) {
--data->refCount;
data = 0;
}
}
+ /// Reset the timer, do not stop it if it's running.
void Reset() {
if (data) data->time = Time();
}
+ /// Restart the timer.
+ /// Only works if the timer was started at least once.
void Restart() {
if (data) {
if (data->target > Time() && data->justHit) {
};
+/// Tracker for timers, responsible for creating and updating them.
template<class Time>
class Timers {
Timers() { }
public:
+ /// Move all timers forward by delta.
void Update(Time delta) {
for (typename std::list<TimerData<Time> >::iterator i(data.begin()), end(data.end()); i != end;) {
if (i->target > 0) {
}
}
}
+ /// Start a timer that counts elapsed time until stopped manually.
Timer<Time> StartTimer() {
data.push_back(TimerData<Time>());
return Timer<Time>(&data.back());
}
+ /// Start a countdown that hits (JustHit() returing true) when duration
+ /// elapsed.
Timer<Time> StartCountdown(Time duration) {
data.push_back(TimerData<Time>(duration, false));
return Timer<Time>(&data.back());
}
+ /// Start an interval timer that hits (JustHit() returning true) each time
+ /// duration has passed.
Timer<Time> StartInterval(Time duration) {
data.push_back(TimerData<Time>(duration, true));
return Timer<Time>(&data.back());
state = mapState;
}
- Application app(&screen, state);
+ Application app(screen, state);
app.Buttons().MapKey(SDLK_w, Input::PAD_UP);
app.Buttons().MapKey(SDLK_d, Input::PAD_RIGHT);
app.Buttons().MapKey(SDLK_s, Input::PAD_DOWN);