123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657 |
- #ifdef EMSCRIPTEN
- #include <emscripten.h>
- #endif
- #include "openbw/ui.h"
- #include "openbw/common.h"
- #include "openbw/bwgame.h"
- #include "openbw/replay.h"
- #include <chrono>
- #include <thread>
- using namespace bwgame;
- using ui::log;
- FILE* log_file = nullptr;
- namespace bwgame {
- namespace ui {
- void log_str(a_string str) {
- fwrite(str.data(), str.size(), 1, stdout);
- fflush(stdout);
- if (!log_file) log_file = fopen("log.txt", "wb");
- if (log_file) {
- fwrite(str.data(), str.size(), 1, log_file);
- fflush(log_file);
- }
- }
- void fatal_error_str(a_string str) {
- #ifdef EMSCRIPTEN
- const char* p = str.c_str();
- EM_ASM_({js_fatal_error($0);}, p);
- #endif
- log("fatal error: %s\n", str);
- std::terminate();
- }
- }
- }
- struct saved_state {
- state st;
- action_state action_st;
- std::array<apm_t, 12> apm;
- };
- struct main_t {
- ui_functions ui;
- main_t(game_player player) : ui(std::move(player), int2(1280,800), true) {}
- std::chrono::high_resolution_clock clock;
- std::chrono::high_resolution_clock::time_point last_tick;
- std::chrono::high_resolution_clock::time_point last_fps;
- int fps_counter = 0;
- a_map<int, std::unique_ptr<saved_state>> saved_states;
- void reset() {
- saved_states.clear();
- ui.reset();
- }
- void update() {
- auto now = clock.now();
- auto tick_speed = std::chrono::milliseconds((fp8::integer(42) / ui.game_speed).integer_part());
- if (now - last_fps >= std::chrono::seconds(1)) {
- //log("game fps: %g\n", fps_counter / std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1, 1>>>(now - last_fps).count());
- last_fps = now;
- fps_counter = 0;
- }
- auto next = [&]() {
- int save_interval = 10 * 1000 / 42;
- if (ui.st.current_frame == 0 || ui.st.current_frame % save_interval == 0) {
- auto i = saved_states.find(ui.st.current_frame);
- if (i == saved_states.end()) {
- auto v = std::make_unique<saved_state>();
- v->st = copy_state(ui.st);
- v->action_st = copy_state(ui.action_st, ui.st, v->st);
- v->apm = ui.apm;
- a_map<int, std::unique_ptr<saved_state>> new_saved_states;
- new_saved_states[ui.st.current_frame] = std::move(v);
- while (!saved_states.empty()) {
- auto i = saved_states.begin();
- auto v = std::move(*i);
- saved_states.erase(i);
- new_saved_states[v.first] = std::move(v.second);
- }
- std::swap(saved_states, new_saved_states);
- }
- }
- ui.replay_functions::next_frame();
- for (auto& v : ui.apm) v.update(ui.st.current_frame);
- };
- if (!ui.is_done() || ui.st.current_frame != ui.replay_frame) {
- if (ui.st.current_frame != ui.replay_frame) {
- if (ui.st.current_frame != ui.replay_frame) {
- auto i = saved_states.lower_bound(ui.replay_frame);
- if (i != saved_states.begin()) --i;
- auto& v = i->second;
- if (ui.st.current_frame > ui.replay_frame || v->st.current_frame > ui.st.current_frame) {
- ui.st = copy_state(v->st);
- ui.action_st = copy_state(v->action_st, v->st, ui.st);
- ui.apm = v->apm;
- }
- }
- if (ui.st.current_frame < ui.replay_frame) {
- for (size_t i = 0; i != 32 && ui.st.current_frame != ui.replay_frame; ++i) {
- for (size_t i2 = 0; i2 != 4 && ui.st.current_frame != ui.replay_frame; ++i2) {
- next();
- }
- if (clock.now() - now >= std::chrono::milliseconds(50)) break;
- }
- }
- last_tick = now;
- } else {
- if (ui.is_paused) {
- last_tick = now;
- } else {
- auto tick_t = now - last_tick;
- if (tick_t >= tick_speed * 16) {
- last_tick = now - tick_speed * 16;
- tick_t = tick_speed * 16;
- }
- auto tick_n = tick_speed.count() == 0 ? 128 : tick_t / tick_speed;
- for (auto i = tick_n; i;) {
- --i;
- ++fps_counter;
- last_tick += tick_speed;
- if (!ui.is_done()) next();
- else break;
- if (i % 4 == 3 && clock.now() - now >= std::chrono::milliseconds(50)) break;
- }
- ui.replay_frame = ui.st.current_frame;
- }
- }
- }
- ui.update();
- }
- };
- main_t* g_m = nullptr;
- uint32_t freemem_rand_state = (uint32_t)std::chrono::high_resolution_clock::now().time_since_epoch().count();
- auto freemem_rand() {
- freemem_rand_state = freemem_rand_state * 22695477 + 1;
- return (freemem_rand_state >> 16) & 0x7fff;
- }
- void out_of_memory() {
- printf("out of memory :(\n");
- #ifdef EMSCRIPTEN
- const char* p = "out of memory :(";
- EM_ASM_({js_fatal_error($0);}, p);
- #endif
- throw std::bad_alloc();
- }
- size_t bytes_allocated = 0;
- void free_memory() {
- if (!g_m) out_of_memory();
- size_t n_states = g_m->saved_states.size();
- printf("n_states is %lu\n", n_states);
- if (n_states <= 2) out_of_memory();
- size_t n;
- if (n_states >= 300) n = 1 + freemem_rand() % (n_states - 2);
- else {
- auto begin = std::next(g_m->saved_states.begin());
- auto end = std::prev(g_m->saved_states.end());
- n = 1;
- int best_score = std::numeric_limits<int>::max();
- size_t i_n = 1;
- for (auto i = begin; i != end; ++i, ++i_n) {
- int score = 0;
- for (auto i2 = begin; i2 != end; ++i2) {
- if (i2 != i) {
- int d = i2->first - i->first;
- score += d*d;
- }
- }
- if (score < best_score) {
- best_score = score;
- n = i_n;
- }
- }
- }
- g_m->saved_states.erase(std::next(g_m->saved_states.begin(), n));
- }
- //extern "C" void set_malloc_fail_handler(bool(*)());
- //bool malloc_fail_handler() {
- // free_memory();
- // return true;
- //}
- // struct dlmalloc_chunk {
- // size_t prev_foot;
- // size_t head;
- // dlmalloc_chunk* fd;
- // dlmalloc_chunk* bk;
- // };
- //
- // size_t alloc_size(void* ptr) {
- // dlmalloc_chunk* c = (dlmalloc_chunk*)((char*)ptr - sizeof(size_t) * 2);
- // return c->head & ~7;
- // }
- //
- // extern "C" void* dlmalloc(size_t);
- // extern "C" void dlfree(void*);
- //
- // size_t max_bytes_allocated = 160 * 1024 * 1024;
- //
- // extern "C" void* malloc(size_t n) {
- // void* r = dlmalloc(n);
- // while (!r) {
- // printf("failed to allocate %d bytes\n", n);
- // free_memory();
- // r = dlmalloc(n);
- // }
- // bytes_allocated += alloc_size(r);
- // while (bytes_allocated > max_bytes_allocated) free_memory();
- // return r;
- // }
- //
- // extern "C" void free(void* ptr) {
- // if (!ptr) return;
- // bytes_allocated -= alloc_size(ptr);
- // dlfree(ptr);
- // }
- #ifdef EMSCRIPTEN
- namespace bwgame {
- namespace data_loading {
- template<bool default_little_endian = true>
- struct js_file_reader {
- a_string filename;
- size_t index = ~(size_t)0;
- size_t file_pointer = 0;
- js_file_reader() = default;
- explicit js_file_reader(a_string filename) {
- open(std::move(filename));
- }
- void open(a_string filename) {
- if (filename == "StarDat.mpq") index = 0;
- else if (filename == "BrooDat.mpq") index = 1;
- else if (filename == "Patch_rt.mpq") index = 2;
- else ui::xcept("js_file_reader: unknown filename '%s'", filename);
- this->filename = std::move(filename);
- }
- void get_bytes(uint8_t* dst, size_t n) {
- EM_ASM_({js_read_data($0, $1, $2, $3);}, index, dst, file_pointer, n);
- file_pointer += n;
- }
- void seek(size_t offset) {
- file_pointer = offset;
- }
- size_t tell() const {
- file_pointer;
- }
- size_t size() {
- return EM_ASM_INT({return js_file_size($0);}, index);
- }
- };
- }
- }
- main_t* m;
- extern "C" double replay_get_value(int index) {
- switch (index) {
- case 0:
- return m->ui.game_speed.raw_value / 256.0;
- case 1:
- return m->ui.is_paused ? 1 : 0;
- case 2:
- return (double)m->ui.st.current_frame;
- case 3:
- return (double)m->ui.replay_frame;
- case 4:
- return (double)m->ui.replay_st.end_frame;
- case 5:
- return (double)(uintptr_t)m->ui.replay_st.map_name.data();
- case 6:
- return (double)m->ui.replay_frame / m->ui.replay_st.end_frame;
- default:
- return 0;
- }
- }
- extern "C" void replay_set_value(int index, double value) {
- switch (index) {
- case 0:
- m->ui.game_speed.raw_value = (int)(value * 256.0);
- if (m->ui.game_speed < 1_fp8) m->ui.game_speed = 1_fp8;
- break;
- case 1:
- m->ui.is_paused = value != 0.0;
- break;
- case 3:
- m->ui.replay_frame = (int)value;
- if (m->ui.replay_frame < 0) m->ui.replay_frame = 0;
- if (m->ui.replay_frame > m->ui.replay_st.end_frame) m->ui.replay_frame = m->ui.replay_st.end_frame;
- break;
- case 6:
- m->ui.replay_frame = (int)(m->ui.replay_st.end_frame * value);
- if (m->ui.replay_frame < 0) m->ui.replay_frame = 0;
- if (m->ui.replay_frame > m->ui.replay_st.end_frame) m->ui.replay_frame = m->ui.replay_st.end_frame;
- break;
- }
- }
- #include <emscripten/bind.h>
- #include <emscripten/val.h>
- using namespace emscripten;
- struct js_unit_type {
- const unit_type_t* ut = nullptr;
- js_unit_type() {}
- js_unit_type(const unit_type_t* ut) : ut(ut) {}
- auto id() const {return ut ? (int)ut->id : 228;}
- auto build_time() const {return ut->build_time;}
- };
- struct js_unit {
- unit_t* u = nullptr;
- js_unit() {}
- js_unit(unit_t* u) : u(u) {}
- auto owner() const {return u->owner;}
- auto remaining_build_time() const {return u->remaining_build_time;}
- auto unit_type() const {return u->unit_type;}
- auto build_type() const {return u->build_queue.empty() ? nullptr : u->build_queue.front();}
- };
- struct util_functions: state_functions {
- util_functions(state& st) : state_functions(st) {}
- double worker_supply(int owner) {
- double r = 0.0;
- for (const unit_t* u : ptr(st.player_units.at(owner))) {
- if (!ut_worker(u)) continue;
- if (!u_completed(u)) continue;
- r += u->unit_type->supply_required.raw_value / 2.0;
- }
- return r;
- }
- double army_supply(int owner) {
- double r = 0.0;
- for (const unit_t* u : ptr(st.player_units.at(owner))) {
- if (ut_worker(u)) continue;
- if (!u_completed(u)) continue;
- r += u->unit_type->supply_required.raw_value / 2.0;
- }
- return r;
- }
- auto get_all_incomplete_units() {
- val r = val::array();
- size_t i = 0;
- for (unit_t* u : ptr(st.visible_units)) {
- if (u_completed(u)) continue;
- r.set(i++, u);
- }
- for (unit_t* u : ptr(st.hidden_units)) {
- if (u_completed(u)) continue;
- r.set(i++, u);
- }
- return r;
- }
- auto get_all_completed_units() {
- val r = val::array();
- size_t i = 0;
- for (unit_t* u : ptr(st.visible_units)) {
- if (!u_completed(u)) continue;
- r.set(i++, u);
- }
- for (unit_t* u : ptr(st.hidden_units)) {
- if (!u_completed(u)) continue;
- r.set(i++, u);
- }
- return r;
- }
- auto get_all_units() {
- val r = val::array();
- size_t i = 0;
- for (unit_t* u : ptr(st.visible_units)) {
- r.set(i++, u);
- }
- for (unit_t* u : ptr(st.hidden_units)) {
- r.set(i++, u);
- }
- for (unit_t* u : ptr(st.map_revealer_units)) {
- r.set(i++, u);
- }
- return r;
- }
- auto get_completed_upgrades(int owner) {
- val r = val::array();
- size_t n = 0;
- for (size_t i = 0; i != 61; ++i) {
- int level = player_upgrade_level(owner, (UpgradeTypes)i);
- if (level == 0) continue;
- val o = val::object();
- o.set("id", val((int)i));
- o.set("icon", val(get_upgrade_type((UpgradeTypes)i)->icon));
- o.set("level", val(level));
- r.set(n++, o);
- }
- return r;
- }
- auto get_completed_research(int owner) {
- val r = val::array();
- size_t n = 0;
- for (size_t i = 0; i != 44; ++i) {
- if (!player_has_researched(owner, (TechTypes)i)) continue;
- val o = val::object();
- o.set("id", val((int)i));
- o.set("icon", val(get_tech_type((TechTypes)i)->icon));
- r.set(n++, o);
- }
- return r;
- }
- auto get_incomplete_upgrades(int owner) {
- val r = val::array();
- size_t i = 0;
- for (unit_t* u : ptr(st.player_units[owner])) {
- if (u->order_type->id == Orders::Upgrade && u->building.upgrading_type) {
- val o = val::object();
- o.set("id", val((int)u->building.upgrading_type->id));
- o.set("icon", val((int)u->building.upgrading_type->icon));
- o.set("level", val(u->building.upgrading_level));
- o.set("remaining_time", val(u->building.upgrade_research_time));
- o.set("total_time", val(upgrade_time_cost(owner, u->building.upgrading_type)));
- r.set(i++, o);
- }
- }
- return r;
- }
- auto get_incomplete_research(int owner) {
- val r = val::array();
- size_t i = 0;
- for (unit_t* u : ptr(st.player_units[owner])) {
- if (u->order_type->id == Orders::ResearchTech && u->building.researching_type) {
- val o = val::object();
- o.set("id", val((int)u->building.researching_type->id));
- o.set("icon", val((int)u->building.researching_type->icon));
- o.set("remaining_time", val(u->building.upgrade_research_time));
- o.set("total_time", val(u->building.researching_type->research_time));
- r.set(i++, o);
- }
- }
- return r;
- }
- };
- optional<util_functions> m_util_funcs;
- util_functions& get_util_funcs() {
- m_util_funcs.emplace(m->ui.st);
- return *m_util_funcs;
- }
- const unit_type_t* unit_t_unit_type(const unit_t* u) {
- return u->unit_type;
- }
- const unit_type_t* unit_t_build_type(const unit_t* u) {
- if (u->build_queue.empty()) return nullptr;
- return u->build_queue.front();
- }
- int unit_type_t_id(const unit_type_t& ut) {
- return (int)ut.id;
- }
- void set_volume(double percent) {
- m->ui.set_volume((int)(percent * 100));
- }
- double get_volume() {
- return m->ui.global_volume / 100.0;
- }
- EMSCRIPTEN_BINDINGS(openbw) {
- register_vector<js_unit>("vector_js_unit");
- class_<util_functions>("util_functions")
- .function("worker_supply", &util_functions::worker_supply)
- .function("army_supply", &util_functions::army_supply)
- .function("get_all_incomplete_units", &util_functions::get_all_incomplete_units, allow_raw_pointers())
- .function("get_all_completed_units", &util_functions::get_all_completed_units, allow_raw_pointers())
- .function("get_all_units", &util_functions::get_all_units, allow_raw_pointers())
- .function("get_completed_upgrades", &util_functions::get_completed_upgrades)
- .function("get_completed_research", &util_functions::get_completed_research)
- .function("get_incomplete_upgrades", &util_functions::get_incomplete_upgrades)
- .function("get_incomplete_research", &util_functions::get_incomplete_research)
- ;
- function("get_util_funcs", &get_util_funcs);
- function("set_volume", &set_volume);
- function("get_volume", &get_volume);
- class_<unit_type_t>("unit_type_t")
- .property("id", &unit_type_t_id)
- .property("build_time", &unit_type_t::build_time)
- ;
- class_<unit_t>("unit_t")
- .property("owner", &unit_t::owner)
- .property("remaining_build_time", &unit_t::remaining_build_time)
- .function("unit_type", &unit_t_unit_type, allow_raw_pointers())
- .function("build_type", &unit_t_build_type, allow_raw_pointers())
- ;
- }
- extern "C" double player_get_value(int player, int index) {
- if (player < 0 || player >= 12) return 0;
- switch (index) {
- case 0:
- return m->ui.st.players.at(player).controller == player_t::controller_occupied ? 1 : 0;
- case 1:
- return (double)m->ui.st.players.at(player).color;
- case 2:
- return (double)(uintptr_t)m->ui.replay_st.player_name.at(player).data();
- case 3:
- return m->ui.st.supply_used.at(player)[0].raw_value / 2.0;
- case 4:
- return m->ui.st.supply_used.at(player)[1].raw_value / 2.0;
- case 5:
- return m->ui.st.supply_used.at(player)[2].raw_value / 2.0;
- case 6:
- return std::min(m->ui.st.supply_available.at(player)[0].raw_value / 2.0, 200.0);
- case 7:
- return std::min(m->ui.st.supply_available.at(player)[1].raw_value / 2.0, 200.0);
- case 8:
- return std::min(m->ui.st.supply_available.at(player)[2].raw_value / 2.0, 200.0);
- case 9:
- return (double)m->ui.st.current_minerals.at(player);
- case 10:
- return (double)m->ui.st.current_gas.at(player);
- case 11:
- return util_functions(m->ui.st).worker_supply(player);
- case 12:
- return util_functions(m->ui.st).army_supply(player);
- case 13:
- return (double)(int)m->ui.st.players.at(player).race;
- case 14:
- return (double)m->ui.apm.at(player).current_apm;
- default:
- return 0;
- }
- }
- bool any_replay_loaded = false;
- extern "C" void load_replay(const uint8_t* data, size_t len) {
- m->reset();
- m->ui.load_replay_data(data, len);
- m->ui.set_image_data();
- any_replay_loaded = true;
- }
- #endif
- int main(int argc, char const* argv[])
- {
- using namespace bwgame;
- log("v25\n");
- std::chrono::high_resolution_clock clock;
- auto start = clock.now();
- #ifdef EMSCRIPTEN
- auto load_data_file = data_loading::data_files_directory<data_loading::data_files_loader<data_loading::mpq_file<data_loading::js_file_reader<>>>>("");
- #else
- auto load_data_file = data_loading::data_files_directory("");
- #endif
- game_player player(load_data_file);
- main_t m(std::move(player));
- auto& ui = m.ui;
- m.ui.load_all_image_data(load_data_file);
- ui.load_data_file = [&](a_vector<uint8_t>& data, a_string filename) {
- load_data_file(data, std::move(filename));
- };
- ui.init();
- #ifndef EMSCRIPTEN
- ui.load_replay_file(argc > 1 ? argv[1] : "maps/p49.rep");
- #endif
- int2 map_size(ui.game_st.map_width, ui.game_st.map_height);
- ui.view.position = (map_size - ui.view.size)/2;
- ui.set_image_data();
- log("loaded in %dms\n", std::chrono::duration_cast<std::chrono::milliseconds>(clock.now() - start).count());
- //set_malloc_fail_handler(malloc_fail_handler);
- #ifdef EMSCRIPTEN
- ::m = &m;
- ::g_m = &m;
- //EM_ASM({js_load_done();});
- emscripten_set_main_loop_arg([](void* ptr) {
- if (!any_replay_loaded) return;
- EM_ASM({js_pre_main_loop();});
- ((main_t*)ptr)->update();
- EM_ASM({js_post_main_loop();});
- }, &m, 0, 1);
- #else
- ::g_m = &m;
- while (true) {
- m.update();
- std::this_thread::sleep_for(std::chrono::milliseconds(20));
- }
- #endif
- ::g_m = nullptr;
- return 0;
- }
|