#include <errno.h>
#include <unistd.h>
#include "maze.hpp"
#include "state.hpp"
/** @brief Main event loop, waiting for input and update state for a new frame. */
static int run(State&& state) {
Renderer::input_t in{};
float curr_delay = ((1000.0F / min_fps) - (1000.0F / max_fps)) / 2.0F;
// poll duration of pre-flight frame for seeding frame delay
(void)state.tick(in);
while (true) {
usleep(10 * 1000);
if (state.duration_hint().get_size() > 0) {
curr_delay = std::min(curr_delay, state.duration_hint().get_sliding_avg());
break;
} else if (state.congestion_hint() > 0) {
break;
}
}
while (true) {
msec_t now = Timer::now();
const msec_t next_draw = now + (msec_t)std::floor(curr_delay);
if (!state.tick(in)) {
LOG("Great success!");
return 0;
} else if (in.action.quit) {
LOG("Exit");
return EINTR;
} else if (in.action.restart) {
LOG("Restart");
return EAGAIN;
} else if (in.action.regen) {
LOG("Re-generate");
return ERESTART;
}
now = Timer::now();
if (now < next_draw) {
usleep(static_cast<unsigned>((next_draw - now) * 1000));
}
do {
in = state.get_input();
} while (in.actions == 0);
curr_delay = state.duration_hint().get_sliding_avg() > curr_delay ? curr_delay * 1.05F : curr_delay * 0.95F;
curr_delay = state.congestion_hint() > 0 ? curr_delay * 1.25F : curr_delay;
curr_delay = std::min(1000.0F / min_fps, std::max(1000.0F / max_fps, SQUEEZE_FACTOR * curr_delay));
#if USE_LOG_STATS > 1
LOG("Delay for %4u ms (%.1f fps)", (unsigned)std::floor(curr_delay), 1000.0F / curr_delay);
#endif
}
}
/**
* @brief As the main loop, but do so without processing input for benchmarking.
*
* Walk the given level waypoints for comparable performance comparisons.
* Can be done without actually drawing images to or reading keys from an X window.
*/
static int run(State&& state, const std::vector<vertex_t>& waypoints) {
const unsigned max_delay = std::ceil(1000.0F / (float)min_fps);
const unsigned min_delay = std::floor(1000.0F / (float)max_fps);
unsigned curr_delay = min_delay;
#if 1
const msec_t min_runtime = Timer::now() + 10000;
const size_t waypoint_samples = 3;
#else
const msec_t min_runtime = 0;
const size_t waypoint_samples = 1;
#endif
do {
for (const auto& waypoint : waypoints) {
Renderer::input_t in{};
in.offset = waypoint;
in.action.beam = 1;
for (size_t i = 0; i < waypoint_samples; ++i) {
in.offset += micro_dist;
(void)state.tick(in);
usleep(curr_delay * 1000);
curr_delay = (unsigned)std::floor(state.duration_hint().get_sliding_avg());
curr_delay += (state.congestion_hint() * min_delay);
curr_delay = std::min(max_delay, std::max(min_delay, curr_delay));
}
}
} while (0 < min_runtime && min_runtime > Timer::now());
return EINTR;
}
/** @brief Given a level seed, generate a maze and run the main loop. */
static int run(PRNG::result_type seed, res_t resolution, bool subsample, bool benchmark) {
using namespace level_config;
const vertex_t raster = vertex_t::setted(raster_base).with_z(1.0F);
std::unique_ptr<const Level> scene = generate(seed, {level_dim[0], level_dim[0]}, {level_dim[1], level_dim[1]},
{level_dim[2], level_dim[2]}, {level_max[0], level_max[1]}, raster);
const fcoord_t sensitivity = raster_base * raster_per_second / 1000.0F; // per msec
const vertex_t camera = vertex_t{scene->start.x + MICRO_DIST, scene->start.y + MICRO_DIST, camera_z} * raster;
const vertex_t viewport =
vertex_t{(fcoord_t)level_dim[2] / 2.0F, (fcoord_t)level_dim[2] / 2.0F, viewport_z} * raster;
if (benchmark) {
return run(State(scene->objects, scene->lights, raster, viewport, camera, resolution, subsample, sensitivity),
scene->rooms);
}
while (true) {
int rv =
run(State(scene->objects, scene->lights, raster, viewport, camera, resolution, subsample, sensitivity));
if (rv != EAGAIN) {
return rv;
}
}
}
int main(int argc, char** argv) {
std::srand(static_cast<unsigned>(Timer::now()));
PRNG::result_type seed = PRNG::hash(static_cast<PRNG::result_type>(std::rand()));
bool benchmark = false;
bool subsample = true; // half internal resolution
int resolution_x = 800; // non-square aspect supported by padding
int resolution_y = 800;
int opt;
while ((opt = getopt(argc, argv, "bfx:y:s:")) != -1) {
switch (opt) {
case 'b':
benchmark = true;
break;
case 'f':
subsample = false;
break;
case 'x':
resolution_x = atoi(optarg);
break;
case 'y':
resolution_y = atoi(optarg);
break;
case 's':
seed = static_cast<PRNG::result_type>(strtoul(optarg, NULL, 10));
break;
default:
return EXIT_FAILURE;
}
}
if (optind != argc) {
return EXIT_FAILURE;
}
while (true) {
int rv = run(seed, {resolution_x, resolution_y}, subsample, benchmark);
if (rv != 0 && rv != ERESTART) {
return EXIT_SUCCESS;
}
seed = PRNG::hash(seed);
}
}