gfxtest.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. #ifdef EMSCRIPTEN
  2. #include <emscripten.h>
  3. #endif
  4. #include "openbw/ui.h"
  5. #include "openbw/common.h"
  6. #include "openbw/bwgame.h"
  7. #include "openbw/replay.h"
  8. #include <chrono>
  9. #include <thread>
  10. using namespace bwgame;
  11. using ui::log;
  12. FILE* log_file = nullptr;
  13. namespace bwgame {
  14. namespace ui {
  15. void log_str(a_string str) {
  16. fwrite(str.data(), str.size(), 1, stdout);
  17. fflush(stdout);
  18. if (!log_file) log_file = fopen("log.txt", "wb");
  19. if (log_file) {
  20. fwrite(str.data(), str.size(), 1, log_file);
  21. fflush(log_file);
  22. }
  23. }
  24. void fatal_error_str(a_string str) {
  25. #ifdef EMSCRIPTEN
  26. const char* p = str.c_str();
  27. EM_ASM_({js_fatal_error($0);}, p);
  28. #endif
  29. log("fatal error: %s\n", str);
  30. std::terminate();
  31. }
  32. }
  33. }
  34. struct saved_state {
  35. state st;
  36. action_state action_st;
  37. std::array<apm_t, 12> apm;
  38. };
  39. struct main_t {
  40. ui_functions ui;
  41. main_t(game_player player) : ui(std::move(player), int2(1280,800), true) {}
  42. std::chrono::high_resolution_clock clock;
  43. std::chrono::high_resolution_clock::time_point last_tick;
  44. std::chrono::high_resolution_clock::time_point last_fps;
  45. int fps_counter = 0;
  46. a_map<int, std::unique_ptr<saved_state>> saved_states;
  47. void reset() {
  48. saved_states.clear();
  49. ui.reset();
  50. }
  51. void update() {
  52. auto now = clock.now();
  53. auto tick_speed = std::chrono::milliseconds((fp8::integer(42) / ui.game_speed).integer_part());
  54. if (now - last_fps >= std::chrono::seconds(1)) {
  55. //log("game fps: %g\n", fps_counter / std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1, 1>>>(now - last_fps).count());
  56. last_fps = now;
  57. fps_counter = 0;
  58. }
  59. auto next = [&]() {
  60. int save_interval = 10 * 1000 / 42;
  61. if (ui.st.current_frame == 0 || ui.st.current_frame % save_interval == 0) {
  62. auto i = saved_states.find(ui.st.current_frame);
  63. if (i == saved_states.end()) {
  64. auto v = std::make_unique<saved_state>();
  65. v->st = copy_state(ui.st);
  66. v->action_st = copy_state(ui.action_st, ui.st, v->st);
  67. v->apm = ui.apm;
  68. a_map<int, std::unique_ptr<saved_state>> new_saved_states;
  69. new_saved_states[ui.st.current_frame] = std::move(v);
  70. while (!saved_states.empty()) {
  71. auto i = saved_states.begin();
  72. auto v = std::move(*i);
  73. saved_states.erase(i);
  74. new_saved_states[v.first] = std::move(v.second);
  75. }
  76. std::swap(saved_states, new_saved_states);
  77. }
  78. }
  79. ui.replay_functions::next_frame();
  80. for (auto& v : ui.apm) v.update(ui.st.current_frame);
  81. };
  82. if (!ui.is_done() || ui.st.current_frame != ui.replay_frame) {
  83. if (ui.st.current_frame != ui.replay_frame) {
  84. if (ui.st.current_frame != ui.replay_frame) {
  85. auto i = saved_states.lower_bound(ui.replay_frame);
  86. if (i != saved_states.begin()) --i;
  87. auto& v = i->second;
  88. if (ui.st.current_frame > ui.replay_frame || v->st.current_frame > ui.st.current_frame) {
  89. ui.st = copy_state(v->st);
  90. ui.action_st = copy_state(v->action_st, v->st, ui.st);
  91. ui.apm = v->apm;
  92. }
  93. }
  94. if (ui.st.current_frame < ui.replay_frame) {
  95. for (size_t i = 0; i != 32 && ui.st.current_frame != ui.replay_frame; ++i) {
  96. for (size_t i2 = 0; i2 != 4 && ui.st.current_frame != ui.replay_frame; ++i2) {
  97. next();
  98. }
  99. if (clock.now() - now >= std::chrono::milliseconds(50)) break;
  100. }
  101. }
  102. last_tick = now;
  103. } else {
  104. if (ui.is_paused) {
  105. last_tick = now;
  106. } else {
  107. auto tick_t = now - last_tick;
  108. if (tick_t >= tick_speed * 16) {
  109. last_tick = now - tick_speed * 16;
  110. tick_t = tick_speed * 16;
  111. }
  112. auto tick_n = tick_speed.count() == 0 ? 128 : tick_t / tick_speed;
  113. for (auto i = tick_n; i;) {
  114. --i;
  115. ++fps_counter;
  116. last_tick += tick_speed;
  117. if (!ui.is_done()) next();
  118. else break;
  119. if (i % 4 == 3 && clock.now() - now >= std::chrono::milliseconds(50)) break;
  120. }
  121. ui.replay_frame = ui.st.current_frame;
  122. }
  123. }
  124. }
  125. ui.update();
  126. }
  127. };
  128. main_t* g_m = nullptr;
  129. uint32_t freemem_rand_state = (uint32_t)std::chrono::high_resolution_clock::now().time_since_epoch().count();
  130. auto freemem_rand() {
  131. freemem_rand_state = freemem_rand_state * 22695477 + 1;
  132. return (freemem_rand_state >> 16) & 0x7fff;
  133. }
  134. void out_of_memory() {
  135. printf("out of memory :(\n");
  136. #ifdef EMSCRIPTEN
  137. const char* p = "out of memory :(";
  138. EM_ASM_({js_fatal_error($0);}, p);
  139. #endif
  140. throw std::bad_alloc();
  141. }
  142. size_t bytes_allocated = 0;
  143. void free_memory() {
  144. if (!g_m) out_of_memory();
  145. size_t n_states = g_m->saved_states.size();
  146. printf("n_states is %lu\n", n_states);
  147. if (n_states <= 2) out_of_memory();
  148. size_t n;
  149. if (n_states >= 300) n = 1 + freemem_rand() % (n_states - 2);
  150. else {
  151. auto begin = std::next(g_m->saved_states.begin());
  152. auto end = std::prev(g_m->saved_states.end());
  153. n = 1;
  154. int best_score = std::numeric_limits<int>::max();
  155. size_t i_n = 1;
  156. for (auto i = begin; i != end; ++i, ++i_n) {
  157. int score = 0;
  158. for (auto i2 = begin; i2 != end; ++i2) {
  159. if (i2 != i) {
  160. int d = i2->first - i->first;
  161. score += d*d;
  162. }
  163. }
  164. if (score < best_score) {
  165. best_score = score;
  166. n = i_n;
  167. }
  168. }
  169. }
  170. g_m->saved_states.erase(std::next(g_m->saved_states.begin(), n));
  171. }
  172. //extern "C" void set_malloc_fail_handler(bool(*)());
  173. //bool malloc_fail_handler() {
  174. // free_memory();
  175. // return true;
  176. //}
  177. // struct dlmalloc_chunk {
  178. // size_t prev_foot;
  179. // size_t head;
  180. // dlmalloc_chunk* fd;
  181. // dlmalloc_chunk* bk;
  182. // };
  183. //
  184. // size_t alloc_size(void* ptr) {
  185. // dlmalloc_chunk* c = (dlmalloc_chunk*)((char*)ptr - sizeof(size_t) * 2);
  186. // return c->head & ~7;
  187. // }
  188. //
  189. // extern "C" void* dlmalloc(size_t);
  190. // extern "C" void dlfree(void*);
  191. //
  192. // size_t max_bytes_allocated = 160 * 1024 * 1024;
  193. //
  194. // extern "C" void* malloc(size_t n) {
  195. // void* r = dlmalloc(n);
  196. // while (!r) {
  197. // printf("failed to allocate %d bytes\n", n);
  198. // free_memory();
  199. // r = dlmalloc(n);
  200. // }
  201. // bytes_allocated += alloc_size(r);
  202. // while (bytes_allocated > max_bytes_allocated) free_memory();
  203. // return r;
  204. // }
  205. //
  206. // extern "C" void free(void* ptr) {
  207. // if (!ptr) return;
  208. // bytes_allocated -= alloc_size(ptr);
  209. // dlfree(ptr);
  210. // }
  211. #ifdef EMSCRIPTEN
  212. namespace bwgame {
  213. namespace data_loading {
  214. template<bool default_little_endian = true>
  215. struct js_file_reader {
  216. a_string filename;
  217. size_t index = ~(size_t)0;
  218. size_t file_pointer = 0;
  219. js_file_reader() = default;
  220. explicit js_file_reader(a_string filename) {
  221. open(std::move(filename));
  222. }
  223. void open(a_string filename) {
  224. if (filename == "StarDat.mpq") index = 0;
  225. else if (filename == "BrooDat.mpq") index = 1;
  226. else if (filename == "Patch_rt.mpq") index = 2;
  227. else ui::xcept("js_file_reader: unknown filename '%s'", filename);
  228. this->filename = std::move(filename);
  229. }
  230. void get_bytes(uint8_t* dst, size_t n) {
  231. EM_ASM_({js_read_data($0, $1, $2, $3);}, index, dst, file_pointer, n);
  232. file_pointer += n;
  233. }
  234. void seek(size_t offset) {
  235. file_pointer = offset;
  236. }
  237. size_t tell() const {
  238. file_pointer;
  239. }
  240. size_t size() {
  241. return EM_ASM_INT({return js_file_size($0);}, index);
  242. }
  243. };
  244. }
  245. }
  246. main_t* m;
  247. extern "C" double replay_get_value(int index) {
  248. switch (index) {
  249. case 0:
  250. return m->ui.game_speed.raw_value / 256.0;
  251. case 1:
  252. return m->ui.is_paused ? 1 : 0;
  253. case 2:
  254. return (double)m->ui.st.current_frame;
  255. case 3:
  256. return (double)m->ui.replay_frame;
  257. case 4:
  258. return (double)m->ui.replay_st.end_frame;
  259. case 5:
  260. return (double)(uintptr_t)m->ui.replay_st.map_name.data();
  261. case 6:
  262. return (double)m->ui.replay_frame / m->ui.replay_st.end_frame;
  263. default:
  264. return 0;
  265. }
  266. }
  267. extern "C" void replay_set_value(int index, double value) {
  268. switch (index) {
  269. case 0:
  270. m->ui.game_speed.raw_value = (int)(value * 256.0);
  271. if (m->ui.game_speed < 1_fp8) m->ui.game_speed = 1_fp8;
  272. break;
  273. case 1:
  274. m->ui.is_paused = value != 0.0;
  275. break;
  276. case 3:
  277. m->ui.replay_frame = (int)value;
  278. if (m->ui.replay_frame < 0) m->ui.replay_frame = 0;
  279. if (m->ui.replay_frame > m->ui.replay_st.end_frame) m->ui.replay_frame = m->ui.replay_st.end_frame;
  280. break;
  281. case 6:
  282. m->ui.replay_frame = (int)(m->ui.replay_st.end_frame * value);
  283. if (m->ui.replay_frame < 0) m->ui.replay_frame = 0;
  284. if (m->ui.replay_frame > m->ui.replay_st.end_frame) m->ui.replay_frame = m->ui.replay_st.end_frame;
  285. break;
  286. }
  287. }
  288. #include <emscripten/bind.h>
  289. #include <emscripten/val.h>
  290. using namespace emscripten;
  291. struct js_unit_type {
  292. const unit_type_t* ut = nullptr;
  293. js_unit_type() {}
  294. js_unit_type(const unit_type_t* ut) : ut(ut) {}
  295. auto id() const {return ut ? (int)ut->id : 228;}
  296. auto build_time() const {return ut->build_time;}
  297. };
  298. struct js_unit {
  299. unit_t* u = nullptr;
  300. js_unit() {}
  301. js_unit(unit_t* u) : u(u) {}
  302. auto owner() const {return u->owner;}
  303. auto remaining_build_time() const {return u->remaining_build_time;}
  304. auto unit_type() const {return u->unit_type;}
  305. auto build_type() const {return u->build_queue.empty() ? nullptr : u->build_queue.front();}
  306. };
  307. struct util_functions: state_functions {
  308. util_functions(state& st) : state_functions(st) {}
  309. double worker_supply(int owner) {
  310. double r = 0.0;
  311. for (const unit_t* u : ptr(st.player_units.at(owner))) {
  312. if (!ut_worker(u)) continue;
  313. if (!u_completed(u)) continue;
  314. r += u->unit_type->supply_required.raw_value / 2.0;
  315. }
  316. return r;
  317. }
  318. double army_supply(int owner) {
  319. double r = 0.0;
  320. for (const unit_t* u : ptr(st.player_units.at(owner))) {
  321. if (ut_worker(u)) continue;
  322. if (!u_completed(u)) continue;
  323. r += u->unit_type->supply_required.raw_value / 2.0;
  324. }
  325. return r;
  326. }
  327. auto get_all_incomplete_units() {
  328. val r = val::array();
  329. size_t i = 0;
  330. for (unit_t* u : ptr(st.visible_units)) {
  331. if (u_completed(u)) continue;
  332. r.set(i++, u);
  333. }
  334. for (unit_t* u : ptr(st.hidden_units)) {
  335. if (u_completed(u)) continue;
  336. r.set(i++, u);
  337. }
  338. return r;
  339. }
  340. auto get_all_completed_units() {
  341. val r = val::array();
  342. size_t i = 0;
  343. for (unit_t* u : ptr(st.visible_units)) {
  344. if (!u_completed(u)) continue;
  345. r.set(i++, u);
  346. }
  347. for (unit_t* u : ptr(st.hidden_units)) {
  348. if (!u_completed(u)) continue;
  349. r.set(i++, u);
  350. }
  351. return r;
  352. }
  353. auto get_all_units() {
  354. val r = val::array();
  355. size_t i = 0;
  356. for (unit_t* u : ptr(st.visible_units)) {
  357. r.set(i++, u);
  358. }
  359. for (unit_t* u : ptr(st.hidden_units)) {
  360. r.set(i++, u);
  361. }
  362. for (unit_t* u : ptr(st.map_revealer_units)) {
  363. r.set(i++, u);
  364. }
  365. return r;
  366. }
  367. auto get_completed_upgrades(int owner) {
  368. val r = val::array();
  369. size_t n = 0;
  370. for (size_t i = 0; i != 61; ++i) {
  371. int level = player_upgrade_level(owner, (UpgradeTypes)i);
  372. if (level == 0) continue;
  373. val o = val::object();
  374. o.set("id", val((int)i));
  375. o.set("icon", val(get_upgrade_type((UpgradeTypes)i)->icon));
  376. o.set("level", val(level));
  377. r.set(n++, o);
  378. }
  379. return r;
  380. }
  381. auto get_completed_research(int owner) {
  382. val r = val::array();
  383. size_t n = 0;
  384. for (size_t i = 0; i != 44; ++i) {
  385. if (!player_has_researched(owner, (TechTypes)i)) continue;
  386. val o = val::object();
  387. o.set("id", val((int)i));
  388. o.set("icon", val(get_tech_type((TechTypes)i)->icon));
  389. r.set(n++, o);
  390. }
  391. return r;
  392. }
  393. auto get_incomplete_upgrades(int owner) {
  394. val r = val::array();
  395. size_t i = 0;
  396. for (unit_t* u : ptr(st.player_units[owner])) {
  397. if (u->order_type->id == Orders::Upgrade && u->building.upgrading_type) {
  398. val o = val::object();
  399. o.set("id", val((int)u->building.upgrading_type->id));
  400. o.set("icon", val((int)u->building.upgrading_type->icon));
  401. o.set("level", val(u->building.upgrading_level));
  402. o.set("remaining_time", val(u->building.upgrade_research_time));
  403. o.set("total_time", val(upgrade_time_cost(owner, u->building.upgrading_type)));
  404. r.set(i++, o);
  405. }
  406. }
  407. return r;
  408. }
  409. auto get_incomplete_research(int owner) {
  410. val r = val::array();
  411. size_t i = 0;
  412. for (unit_t* u : ptr(st.player_units[owner])) {
  413. if (u->order_type->id == Orders::ResearchTech && u->building.researching_type) {
  414. val o = val::object();
  415. o.set("id", val((int)u->building.researching_type->id));
  416. o.set("icon", val((int)u->building.researching_type->icon));
  417. o.set("remaining_time", val(u->building.upgrade_research_time));
  418. o.set("total_time", val(u->building.researching_type->research_time));
  419. r.set(i++, o);
  420. }
  421. }
  422. return r;
  423. }
  424. };
  425. optional<util_functions> m_util_funcs;
  426. util_functions& get_util_funcs() {
  427. m_util_funcs.emplace(m->ui.st);
  428. return *m_util_funcs;
  429. }
  430. const unit_type_t* unit_t_unit_type(const unit_t* u) {
  431. return u->unit_type;
  432. }
  433. const unit_type_t* unit_t_build_type(const unit_t* u) {
  434. if (u->build_queue.empty()) return nullptr;
  435. return u->build_queue.front();
  436. }
  437. int unit_type_t_id(const unit_type_t& ut) {
  438. return (int)ut.id;
  439. }
  440. void set_volume(double percent) {
  441. m->ui.set_volume((int)(percent * 100));
  442. }
  443. double get_volume() {
  444. return m->ui.global_volume / 100.0;
  445. }
  446. EMSCRIPTEN_BINDINGS(openbw) {
  447. register_vector<js_unit>("vector_js_unit");
  448. class_<util_functions>("util_functions")
  449. .function("worker_supply", &util_functions::worker_supply)
  450. .function("army_supply", &util_functions::army_supply)
  451. .function("get_all_incomplete_units", &util_functions::get_all_incomplete_units, allow_raw_pointers())
  452. .function("get_all_completed_units", &util_functions::get_all_completed_units, allow_raw_pointers())
  453. .function("get_all_units", &util_functions::get_all_units, allow_raw_pointers())
  454. .function("get_completed_upgrades", &util_functions::get_completed_upgrades)
  455. .function("get_completed_research", &util_functions::get_completed_research)
  456. .function("get_incomplete_upgrades", &util_functions::get_incomplete_upgrades)
  457. .function("get_incomplete_research", &util_functions::get_incomplete_research)
  458. ;
  459. function("get_util_funcs", &get_util_funcs);
  460. function("set_volume", &set_volume);
  461. function("get_volume", &get_volume);
  462. class_<unit_type_t>("unit_type_t")
  463. .property("id", &unit_type_t_id)
  464. .property("build_time", &unit_type_t::build_time)
  465. ;
  466. class_<unit_t>("unit_t")
  467. .property("owner", &unit_t::owner)
  468. .property("remaining_build_time", &unit_t::remaining_build_time)
  469. .function("unit_type", &unit_t_unit_type, allow_raw_pointers())
  470. .function("build_type", &unit_t_build_type, allow_raw_pointers())
  471. ;
  472. }
  473. extern "C" double player_get_value(int player, int index) {
  474. if (player < 0 || player >= 12) return 0;
  475. switch (index) {
  476. case 0:
  477. return m->ui.st.players.at(player).controller == player_t::controller_occupied ? 1 : 0;
  478. case 1:
  479. return (double)m->ui.st.players.at(player).color;
  480. case 2:
  481. return (double)(uintptr_t)m->ui.replay_st.player_name.at(player).data();
  482. case 3:
  483. return m->ui.st.supply_used.at(player)[0].raw_value / 2.0;
  484. case 4:
  485. return m->ui.st.supply_used.at(player)[1].raw_value / 2.0;
  486. case 5:
  487. return m->ui.st.supply_used.at(player)[2].raw_value / 2.0;
  488. case 6:
  489. return std::min(m->ui.st.supply_available.at(player)[0].raw_value / 2.0, 200.0);
  490. case 7:
  491. return std::min(m->ui.st.supply_available.at(player)[1].raw_value / 2.0, 200.0);
  492. case 8:
  493. return std::min(m->ui.st.supply_available.at(player)[2].raw_value / 2.0, 200.0);
  494. case 9:
  495. return (double)m->ui.st.current_minerals.at(player);
  496. case 10:
  497. return (double)m->ui.st.current_gas.at(player);
  498. case 11:
  499. return util_functions(m->ui.st).worker_supply(player);
  500. case 12:
  501. return util_functions(m->ui.st).army_supply(player);
  502. case 13:
  503. return (double)(int)m->ui.st.players.at(player).race;
  504. case 14:
  505. return (double)m->ui.apm.at(player).current_apm;
  506. default:
  507. return 0;
  508. }
  509. }
  510. bool any_replay_loaded = false;
  511. extern "C" void load_replay(const uint8_t* data, size_t len) {
  512. m->reset();
  513. m->ui.load_replay_data(data, len);
  514. m->ui.set_image_data();
  515. any_replay_loaded = true;
  516. }
  517. #endif
  518. int main(int argc, char const* argv[])
  519. {
  520. using namespace bwgame;
  521. log("v25\n");
  522. std::chrono::high_resolution_clock clock;
  523. auto start = clock.now();
  524. #ifdef EMSCRIPTEN
  525. auto load_data_file = data_loading::data_files_directory<data_loading::data_files_loader<data_loading::mpq_file<data_loading::js_file_reader<>>>>("");
  526. #else
  527. auto load_data_file = data_loading::data_files_directory("");
  528. #endif
  529. game_player player(load_data_file);
  530. main_t m(std::move(player));
  531. auto& ui = m.ui;
  532. m.ui.load_all_image_data(load_data_file);
  533. ui.load_data_file = [&](a_vector<uint8_t>& data, a_string filename) {
  534. load_data_file(data, std::move(filename));
  535. };
  536. ui.init();
  537. #ifndef EMSCRIPTEN
  538. ui.load_replay_file(argc > 1 ? argv[1] : "maps/p49.rep");
  539. #endif
  540. int2 map_size(ui.game_st.map_width, ui.game_st.map_height);
  541. ui.view.position = (map_size - ui.view.size)/2;
  542. ui.set_image_data();
  543. log("loaded in %dms\n", std::chrono::duration_cast<std::chrono::milliseconds>(clock.now() - start).count());
  544. //set_malloc_fail_handler(malloc_fail_handler);
  545. #ifdef EMSCRIPTEN
  546. ::m = &m;
  547. ::g_m = &m;
  548. //EM_ASM({js_load_done();});
  549. emscripten_set_main_loop_arg([](void* ptr) {
  550. if (!any_replay_loaded) return;
  551. EM_ASM({js_pre_main_loop();});
  552. ((main_t*)ptr)->update();
  553. EM_ASM({js_post_main_loop();});
  554. }, &m, 0, 1);
  555. #else
  556. ::g_m = &m;
  557. while (true) {
  558. m.update();
  559. std::this_thread::sleep_for(std::chrono::milliseconds(20));
  560. }
  561. #endif
  562. ::g_m = nullptr;
  563. return 0;
  564. }