#ifndef BLANK_APP_APPLICATION_HPP_
#define BLANK_APP_APPLICATION_HPP_
-#include "Assets.hpp"
-#include "FrameCounter.hpp"
-#include "../ai/Spawner.hpp"
-#include "../audio/Audio.hpp"
-#include "../graphics/Viewport.hpp"
-#include "../ui/Interface.hpp"
-#include "../world/World.hpp"
-
#include <SDL.h>
+#include <stack>
namespace blank {
+class Environment;
+class State;
class Window;
class Application {
public:
- struct Config {
- bool vsync = true;
- bool doublebuf = true;
- int multisampling = 1;
-
- Interface::Config interface = Interface::Config();
- World::Config world = World::Config();
- };
-
- Application(Window &, const Config &);
+ explicit Application(Environment &);
~Application();
Application(const Application &) = delete;
/// process all events in SDL's queue
void HandleEvents();
+ void Handle(const SDL_Event &);
void Handle(const SDL_WindowEvent &);
/// integrate to the next step with dt milliseconds passed
void Update(int dt);
/// push the current state to display
void Render();
-private:
- Window &window;
- Viewport viewport;
- Assets assets;
- Audio audio;
- FrameCounter counter;
+ void PushState(State *);
+ State *PopState();
+ State *SwitchState(State *);
+ State &GetState();
+ bool HasState() const noexcept;
- World world;
- Interface interface;
-
- Spawner spawner;
-
- bool running;
+private:
+ Environment &env;
+ std::stack<State *> states;
};
--- /dev/null
+#ifndef BLANK_APP_ENVIRONMENT_HPP_
+#define BLANK_APP_ENVIRONMENT_HPP_
+
+#include "Assets.hpp"
+#include "FrameCounter.hpp"
+#include "StateControl.hpp"
+#include "../audio/Audio.hpp"
+#include "../graphics/Viewport.hpp"
+
+
+namespace blank {
+
+class Window;
+
+struct Environment {
+
+ Audio audio;
+ Viewport viewport;
+ Window &window;
+
+ Assets assets;
+ FrameCounter counter;
+
+ StateControl state;
+
+
+ explicit Environment(Window &win);
+
+};
+
+}
+
+#endif
+++ /dev/null
-#include "Runtime.hpp"
-
-#include "init.hpp"
-
-#include <cctype>
-#include <cstdlib>
-#include <iostream>
-
-using namespace std;
-
-
-namespace blank {
-
-Runtime::Runtime() noexcept
-: name("blank")
-, mode(NORMAL)
-, n(0)
-, t(0)
-, config() {
-
-}
-
-
-void Runtime::ReadArgs(int argc, const char *const *argv) {
- if (argc <= 0) return;
- name = argv[0];
-
- bool options = true;
- bool error = false;
-
- for (int i = 1; i < argc; ++i) {
- const char *arg = argv[i];
- if (!arg || arg[0] == '\0') {
- cerr << "warning: found empty argument at position " << i << endl;
- continue;
- }
- if (options && arg[0] == '-') {
- if (arg[1] == '\0') {
- cerr << "warning: incomplete option list at position " << i << endl;
- } else if (arg[1] == '-') {
- if (arg[2] == '\0') {
- // stopper
- options = false;
- } else {
- // long option
- if (strcmp(arg + 2, "no-vsync") == 0) {
- config.vsync = false;
- } else if (strcmp(arg + 2, "no-keyboard") == 0) {
- config.interface.keyboard_disabled = true;
- } else if (strcmp(arg + 2, "no-mouse") == 0) {
- config.interface.mouse_disabled = true;
- } else if (strcmp(arg + 2, "no-hud") == 0) {
- config.interface.visual_disabled = true;
- } else if (strcmp(arg + 2, "no-audio") == 0) {
- config.interface.audio_disabled = true;
- } else {
- cerr << "unknown option " << arg << endl;
- error = true;
- }
- }
- } else {
- // short options
- for (int j = 1; arg[j] != '\0'; ++j) {
- switch (arg[j]) {
- case 'd':
- config.doublebuf = false;
- break;
- case 'm':
- ++i;
- if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
- cerr << "missing argument to -m" << endl;
- error = true;
- } else {
- config.multisampling = strtoul(argv[i], nullptr, 10);
- }
- break;
- case 'n':
- ++i;
- if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
- cerr << "missing argument to -n" << endl;
- error = true;
- } else {
- n = strtoul(argv[i], nullptr, 10);
- }
- break;
- case 's':
- ++i;
- if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
- cerr << "missing argument to -s" << endl;
- error = true;
- } else {
- config.world.gen.solid_seed = strtoul(argv[i], nullptr, 10);
- config.world.gen.type_seed = config.world.gen.solid_seed;
- }
- break;
- case 't':
- ++i;
- if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
- cerr << "missing argument to -t" << endl;
- error = true;
- } else {
- t = strtoul(argv[i], nullptr, 10);
- }
- break;
- case '-':
- // stopper
- options = false;
- break;
- default:
- cerr << "unknown option " << arg[j] << endl;
- error = true;
- break;
- }
- }
- }
- } else {
- cerr << "unable to interpret argument "
- << i << " (" << arg << ")" << endl;
- error = true;
- }
- }
-
- if (error) {
- mode = ERROR;
- return;
- }
-
- if (n > 0) {
- if (t > 0) {
- mode = FIXED_FRAME_LIMIT;
- } else {
- mode = FRAME_LIMIT;
- }
- } else if (t > 0) {
- mode = TIME_LIMIT;
- } else {
- mode = NORMAL;
- }
-}
-
-int Runtime::Execute() {
- if (mode == ERROR) {
- return 1;
- }
-
- Init init(config.doublebuf, config.multisampling);
- Application app(init.window, config);
- switch (mode) {
- default:
- case NORMAL:
- app.Run();
- break;
- case FRAME_LIMIT:
- app.RunN(n);
- break;
- case TIME_LIMIT:
- app.RunT(t);
- break;
- case FIXED_FRAME_LIMIT:
- app.RunS(n, t);
- break;
- }
- return 0;
-}
-
-}
#ifndef BLANK_RUNTIME_HPP_
#define BLANK_RUNTIME_HPP_
-#include "Application.hpp"
+#include "../ui/Interface.hpp"
+#include "../world/World.hpp"
#include <cstddef>
ERROR,
};
+ struct Config {
+ bool vsync = true;
+ bool doublebuf = true;
+ int multisampling = 1;
+
+ Interface::Config interface = Interface::Config();
+ World::Config world = World::Config();
+ };
+
Runtime() noexcept;
void ReadArgs(int argc, const char *const *argv);
Mode mode;
std::size_t n;
std::size_t t;
- Application::Config config;
+ Config config;
};
--- /dev/null
+#ifndef BLANK_APP_STATE_HPP_
+#define BLANK_APP_STATE_HPP_
+
+#include <SDL.h>
+
+
+namespace blank {
+
+class Viewport;
+
+struct State {
+
+ virtual void Handle(const SDL_Event &) = 0;
+
+ virtual void Update(int dt) = 0;
+
+ virtual void Render(Viewport &) = 0;
+
+};
+
+};
+
+#endif
--- /dev/null
+#ifndef BLANK_APP_STATECONTROL_HPP_
+#define BLANK_APP_STATECONTROL_HPP_
+
+#include <queue>
+
+
+namespace blank {
+
+class Application;
+class State;
+
+class StateControl {
+
+public:
+ void Push(State *s) {
+ cue.emplace(PUSH, s);
+ }
+
+ void Switch(State *s) {
+ cue.emplace(SWITCH, s);
+ }
+
+ void Pop() {
+ cue.emplace(POP);
+ }
+
+ void PopAll() {
+ cue.emplace(POP_ALL);
+ }
+
+
+ void Commit(Application &);
+
+private:
+ enum Command {
+ PUSH,
+ SWITCH,
+ POP,
+ POP_ALL,
+ };
+ struct Memo {
+ State *state;
+ Command cmd;
+ explicit Memo(Command c, State *s = nullptr): state(s), cmd(c) { }
+ };
+ std::queue<Memo> cue;
+
+};
+
+}
+
+#endif
--- /dev/null
+#include "WorldState.hpp"
+
+#include "Environment.hpp"
+
+#include <SDL.h>
+
+
+namespace blank {
+
+WorldState::WorldState(
+ Environment &env,
+ const Interface::Config &ic,
+ const World::Config &wc
+)
+: env(env)
+, world(wc)
+, spawner(world)
+, interface(ic, env, world) {
+
+}
+
+
+void WorldState::Handle(const SDL_Event &event) {
+ switch (event.type) {
+ case SDL_KEYDOWN:
+ interface.HandlePress(event.key);
+ break;
+ case SDL_KEYUP:
+ interface.HandleRelease(event.key);
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ interface.HandlePress(event.button);
+ break;
+ case SDL_MOUSEBUTTONUP:
+ interface.HandleRelease(event.button);
+ break;
+ case SDL_MOUSEMOTION:
+ interface.Handle(event.motion);
+ break;
+ case SDL_MOUSEWHEEL:
+ interface.Handle(event.wheel);
+ break;
+ default:
+ break;
+ }
+}
+
+void WorldState::Update(int dt) {
+ interface.Update(dt);
+ spawner.Update(dt);
+ world.Update(dt);
+
+ glm::mat4 trans = world.Player().Transform(Chunk::Pos(0, 0, 0));
+ glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
+ glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
+ env.audio.Position(world.Player().Position());
+ env.audio.Velocity(world.Player().Velocity());
+ env.audio.Orientation(dir, up);
+
+}
+
+void WorldState::Render(Viewport &viewport) {
+ world.Render(viewport);
+ interface.Render(viewport);
+}
+
+}
--- /dev/null
+#ifndef BLANK_APP_WORLDSTATE_HPP_
+#define BLANK_APP_WORLDSTATE_HPP_
+
+#include "State.hpp"
+#include "../ai/Spawner.hpp"
+#include "../ui/Interface.hpp"
+#include "../world/World.hpp"
+
+
+namespace blank {
+
+class Environment;
+
+class WorldState
+: public State {
+
+public:
+ WorldState(
+ Environment &,
+ const Interface::Config &,
+ const World::Config &
+ );
+
+ void Handle(const SDL_Event &) override;
+ void Update(int dt) override;
+ void Render(Viewport &) override;
+
+private:
+ Environment &env;
+ World world;
+ Spawner spawner;
+ Interface interface;
+
+};
+
+}
+
+#endif
#include "Application.hpp"
#include "Assets.hpp"
+#include "Environment.hpp"
#include "FrameCounter.hpp"
+#include "State.hpp"
+#include "StateControl.hpp"
#include "init.hpp"
#include "../audio/Sound.hpp"
using std::string;
-namespace {
-
-string get_asset_path() {
- char *base = SDL_GetBasePath();
- string assets(base);
- assets += "assets/";
- SDL_free(base);
- return assets;
-}
-
-}
-
namespace blank {
-Application::Application(Window &win, const Config &config)
-: window(win)
-, viewport()
-, assets(get_asset_path())
-, audio()
-, counter()
-, world(config.world)
-, interface(config.interface, assets, audio, counter, world)
-, spawner(world)
-, running(false) {
- viewport.VSync(config.vsync);
+Application::Application(Environment &e)
+: env(e)
+, states() {
+
}
Application::~Application() {
- audio.StopAll();
+ env.audio.StopAll();
}
void Application::RunN(size_t n) {
Uint32 last = SDL_GetTicks();
- for (size_t i = 0; i < n; ++i) {
+ for (size_t i = 0; HasState() && i < n; ++i) {
Uint32 now = SDL_GetTicks();
int delta = now - last;
Loop(delta);
void Application::RunT(size_t t) {
Uint32 last = SDL_GetTicks();
Uint32 finish = last + t;
- while (last < finish) {
+ while (HasState() && last < finish) {
Uint32 now = SDL_GetTicks();
int delta = now - last;
Loop(delta);
}
void Application::RunS(size_t n, size_t t) {
- for (size_t i = 0; i < n; ++i) {
+ for (size_t i = 0; HasState() && i < n; ++i) {
Loop(t);
}
}
void Application::Run() {
- running = true;
Uint32 last = SDL_GetTicks();
- window.GrabMouse();
- while (running) {
+ env.window.GrabMouse();
+ while (HasState()) {
Uint32 now = SDL_GetTicks();
int delta = now - last;
Loop(delta);
}
void Application::Loop(int dt) {
- counter.EnterFrame();
+ env.counter.EnterFrame();
HandleEvents();
+ if (!HasState()) return;
Update(dt);
+ env.state.Commit(*this);
+ if (!HasState()) return;
Render();
- counter.ExitFrame();
+ env.counter.ExitFrame();
}
void Application::HandleEvents() {
- counter.EnterHandle();
+ env.counter.EnterHandle();
SDL_Event event;
- while (SDL_PollEvent(&event)) {
- switch (event.type) {
- case SDL_KEYDOWN:
- interface.HandlePress(event.key);
- break;
- case SDL_KEYUP:
- interface.HandleRelease(event.key);
- break;
- case SDL_MOUSEBUTTONDOWN:
- interface.HandlePress(event.button);
- break;
- case SDL_MOUSEBUTTONUP:
- interface.HandleRelease(event.button);
- break;
- case SDL_MOUSEMOTION:
- interface.Handle(event.motion);
- break;
- case SDL_MOUSEWHEEL:
- interface.Handle(event.wheel);
- break;
- case SDL_QUIT:
- running = false;
- break;
- case SDL_WINDOWEVENT:
- Handle(event.window);
- break;
- default:
- break;
- }
+ while (HasState() && SDL_PollEvent(&event)) {
+ Handle(event);
+ env.state.Commit(*this);
+ }
+ env.counter.ExitHandle();
+}
+
+void Application::Handle(const SDL_Event &event) {
+ switch (event.type) {
+ case SDL_QUIT:
+ env.state.PopAll();
+ break;
+ case SDL_WINDOWEVENT:
+ Handle(event.window);
+ break;
+ default:
+ GetState().Handle(event);
+ break;
}
- counter.ExitHandle();
}
void Application::Handle(const SDL_WindowEvent &event) {
switch (event.event) {
case SDL_WINDOWEVENT_FOCUS_GAINED:
- window.GrabMouse();
+ env.window.GrabMouse();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
- window.ReleaseMouse();
+ env.window.ReleaseMouse();
break;
case SDL_WINDOWEVENT_RESIZED:
- viewport.Resize(event.data1, event.data2);
+ env.viewport.Resize(event.data1, event.data2);
break;
default:
break;
}
void Application::Update(int dt) {
- counter.EnterUpdate();
- interface.Update(dt);
- spawner.Update(dt);
- world.Update(dt);
-
- glm::mat4 trans = world.Player().Transform(Chunk::Pos(0, 0, 0));
- glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
- glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
- audio.Position(world.Player().Position());
- audio.Velocity(world.Player().Velocity());
- audio.Orientation(dir, up);
-
- counter.ExitUpdate();
+ env.counter.EnterUpdate();
+ if (HasState()) {
+ GetState().Update(dt);
+ }
+ env.counter.ExitUpdate();
}
void Application::Render() {
// gl implementation may (and will probably) delay vsync blocking until
// the first write after flipping, which is this clear call
- viewport.Clear();
- counter.EnterRender();
+ env.viewport.Clear();
+ env.counter.EnterRender();
+
+ if (HasState()) {
+ GetState().Render(env.viewport);
+ }
+
+ env.counter.ExitRender();
+ env.window.Flip();
+}
- world.Render(viewport);
- interface.Render(viewport);
- counter.ExitRender();
- window.Flip();
+void Application::PushState(State *s) {
+ states.emplace(s);
+}
+
+State *Application::PopState() {
+ State *s = states.top();
+ states.pop();
+ return s;
+}
+
+State *Application::SwitchState(State *s_new) {
+ State *s_old = states.top();
+ states.top() = s_new;
+ return s_old;
+}
+
+State &Application::GetState() {
+ return *states.top();
+}
+
+bool Application::HasState() const noexcept {
+ return !states.empty();
+}
+
+
+void StateControl::Commit(Application &app) {
+ while (!cue.empty()) {
+ Memo m(cue.front());
+ cue.pop();
+ switch (m.cmd) {
+ case PUSH:
+ app.PushState(m.state);
+ break;
+ case SWITCH:
+ app.SwitchState(m.state);
+ break;
+ case POP:
+ app.PopState();
+ break;
+ case POP_ALL:
+ while (app.HasState()) {
+ app.PopState();
+ }
+ break;
+ }
+ }
}
--- /dev/null
+#include "Application.hpp"
+#include "Environment.hpp"
+#include "Runtime.hpp"
+#include "WorldState.hpp"
+
+#include "init.hpp"
+
+#include <cctype>
+#include <cstdlib>
+#include <iostream>
+#include <SDL.h>
+
+using namespace std;
+
+
+namespace {
+
+string get_asset_path() {
+ char *base = SDL_GetBasePath();
+ string assets(base);
+ assets += "assets/";
+ SDL_free(base);
+ return assets;
+}
+
+}
+
+namespace blank {
+
+Environment::Environment(Window &win)
+: audio()
+, viewport()
+, window(win)
+, assets(get_asset_path())
+, counter() {
+
+}
+
+
+Runtime::Runtime() noexcept
+: name("blank")
+, mode(NORMAL)
+, n(0)
+, t(0)
+, config() {
+
+}
+
+
+void Runtime::ReadArgs(int argc, const char *const *argv) {
+ if (argc <= 0) return;
+ name = argv[0];
+
+ bool options = true;
+ bool error = false;
+
+ for (int i = 1; i < argc; ++i) {
+ const char *arg = argv[i];
+ if (!arg || arg[0] == '\0') {
+ cerr << "warning: found empty argument at position " << i << endl;
+ continue;
+ }
+ if (options && arg[0] == '-') {
+ if (arg[1] == '\0') {
+ cerr << "warning: incomplete option list at position " << i << endl;
+ } else if (arg[1] == '-') {
+ if (arg[2] == '\0') {
+ // stopper
+ options = false;
+ } else {
+ // long option
+ if (strcmp(arg + 2, "no-vsync") == 0) {
+ config.vsync = false;
+ } else if (strcmp(arg + 2, "no-keyboard") == 0) {
+ config.interface.keyboard_disabled = true;
+ } else if (strcmp(arg + 2, "no-mouse") == 0) {
+ config.interface.mouse_disabled = true;
+ } else if (strcmp(arg + 2, "no-hud") == 0) {
+ config.interface.visual_disabled = true;
+ } else if (strcmp(arg + 2, "no-audio") == 0) {
+ config.interface.audio_disabled = true;
+ } else {
+ cerr << "unknown option " << arg << endl;
+ error = true;
+ }
+ }
+ } else {
+ // short options
+ for (int j = 1; arg[j] != '\0'; ++j) {
+ switch (arg[j]) {
+ case 'd':
+ config.doublebuf = false;
+ break;
+ case 'm':
+ ++i;
+ if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+ cerr << "missing argument to -m" << endl;
+ error = true;
+ } else {
+ config.multisampling = strtoul(argv[i], nullptr, 10);
+ }
+ break;
+ case 'n':
+ ++i;
+ if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+ cerr << "missing argument to -n" << endl;
+ error = true;
+ } else {
+ n = strtoul(argv[i], nullptr, 10);
+ }
+ break;
+ case 's':
+ ++i;
+ if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+ cerr << "missing argument to -s" << endl;
+ error = true;
+ } else {
+ config.world.gen.solid_seed = strtoul(argv[i], nullptr, 10);
+ config.world.gen.type_seed = config.world.gen.solid_seed;
+ }
+ break;
+ case 't':
+ ++i;
+ if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+ cerr << "missing argument to -t" << endl;
+ error = true;
+ } else {
+ t = strtoul(argv[i], nullptr, 10);
+ }
+ break;
+ case '-':
+ // stopper
+ options = false;
+ break;
+ default:
+ cerr << "unknown option " << arg[j] << endl;
+ error = true;
+ break;
+ }
+ }
+ }
+ } else {
+ cerr << "unable to interpret argument "
+ << i << " (" << arg << ")" << endl;
+ error = true;
+ }
+ }
+
+ if (error) {
+ mode = ERROR;
+ return;
+ }
+
+ if (n > 0) {
+ if (t > 0) {
+ mode = FIXED_FRAME_LIMIT;
+ } else {
+ mode = FRAME_LIMIT;
+ }
+ } else if (t > 0) {
+ mode = TIME_LIMIT;
+ } else {
+ mode = NORMAL;
+ }
+}
+
+int Runtime::Execute() {
+ if (mode == ERROR) {
+ return 1;
+ }
+
+ Init init(config.doublebuf, config.multisampling);
+
+ Environment env(init.window);
+ env.viewport.VSync(config.vsync);
+
+ Application app(env);
+
+ WorldState state(env, config.interface, config.world);
+ app.PushState(&state);
+
+ switch (mode) {
+ default:
+ case NORMAL:
+ app.Run();
+ break;
+ case FRAME_LIMIT:
+ app.RunN(n);
+ break;
+ case TIME_LIMIT:
+ app.RunT(t);
+ break;
+ case FIXED_FRAME_LIMIT:
+ app.RunS(n, t);
+ break;
+ }
+
+ return 0;
+}
+
+}
namespace blank {
-class Assets;
-class Audio;
class Chunk;
-class FrameCounter;
+class Environment;
class Viewport;
class World;
bool visual_disabled = false;
};
- Interface(const Config &, const Assets &, Audio &, const FrameCounter &, World &);
+ Interface(const Config &, Environment &, World &);
void HandlePress(const SDL_KeyboardEvent &);
void HandleRelease(const SDL_KeyboardEvent &);
void CheckAim();
private:
- Audio &audio;
- const FrameCounter &counter;
+ Environment &env;
World &world;
FPSController ctrl;
Font font;
#include "Interface.hpp"
#include "../app/Assets.hpp"
+#include "../app/Environment.hpp"
#include "../app/FrameCounter.hpp"
#include "../app/init.hpp"
#include "../audio/Audio.hpp"
Interface::Interface(
const Config &config,
- const Assets &assets,
- Audio &audio,
- const FrameCounter &counter,
+ Environment &env,
World &world)
-: audio(audio)
-, counter(counter)
+: env(env)
, world(world)
, ctrl(world.Player())
-, font(assets.LoadFont("DejaVuSans", 16))
+, font(env.assets.LoadFont("DejaVuSans", 16))
, hud(world.BlockTypes(), font)
, aim{{ 0, 0, 0 }, { 0, 0, -1 }}
, aim_chunk(nullptr)
, remove_timer(256)
, remove(0)
, selection(1)
-, place_sound(assets.LoadSound("thump"))
-, remove_sound(assets.LoadSound("plop"))
+, place_sound(env.assets.LoadSound("thump"))
+, remove_sound(env.assets.LoadSound("plop"))
, fwd(0)
, rev(0) {
counter_text.Hide();
void Interface::UpdateCounter() {
std::stringstream s;
s << std::setprecision(3) <<
- "avg: " << counter.Average().running << "ms, "
- "peak: " << counter.Peak().running << "ms";
+ "avg: " << env.counter.Average().running << "ms, "
+ "peak: " << env.counter.Peak().running << "ms";
std::string text = s.str();
counter_text.Set(font, text);
}
if (config.audio_disabled) return;
const Entity &player = ctrl.Controlled();
- audio.Play(
+ env.audio.Play(
place_sound,
mod_chunk->ToSceneCoords(player.ChunkCoords(), next_pos)
);
if (config.audio_disabled) return;
const Entity &player = ctrl.Controlled();
- audio.Play(
+ env.audio.Play(
remove_sound,
aim_chunk->ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(aim_block))
);
CheckAim();
}
- if (counter_text.Visible() && counter.Changed()) {
+ if (counter_text.Visible() && env.counter.Changed()) {
UpdateCounter();
}
if (position_text.Visible()) {