1 // Hyperbolic Rogue -- memory saving
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3
4 /** \file memory.cpp
5 * \brief memory saving: memory saving mode, memory warnings, etc.
6 */
7
8 #include "hyper.h"
9 namespace hr {
10
11 #if HDR
12 static const int PSEUDOKEY_MEMORY = 16397;
13 #endif
14
15 EX bool memory_saving_mode = true;
16
17 EX bool show_memory_warning = true;
18 EX bool ignored_memory_warning;
19
20 static const int LIM = 150;
21
22 EX heptagon *last_cleared;
23
destroycellcontents(cell * c)24 EX void destroycellcontents(cell *c) {
25 c->land = laMemory;
26 c->wall = waChasm;
27 c->item = itNone;
28 if(!isMultitile(c->monst) && c->monst != moPair)
29 c->monst = moNone;
30 }
31
degrade(cell * c)32 void degrade(cell *c) {
33 c->mpdist++;
34 forCellEx(c2, c)
35 if(c2->mpdist < c->mpdist - 1)
36 degrade(c2);
37 destroycellcontents(c);
38 }
39
40 EX vector<cell*> removed_cells;
41
slow_delete_cell(cell * c)42 void slow_delete_cell(cell *c) {
43 while(c->mpdist < BARLEV)
44 degrade(c);
45 for(int i=0; i<c->type; i++)
46 if(c->move(i))
47 c->move(i)->move(c->c.spin(i)) = NULL;
48 removed_cells.push_back(c);
49 delete c;
50 }
51
delete_heptagon(heptagon * h2)52 void delete_heptagon(heptagon *h2) {
53 cell *c = h2->c7;
54 if(BITRUNCATED) {
55 for(int i=0; i<c->type; i++)
56 if(c->move(i))
57 slow_delete_cell(c->move(i));
58 }
59 slow_delete_cell(c);
60 for(int i=0; i<S7; i++)
61 if(h2->move(i))
62 h2->move(i)->move(h2->c.spin(i)) = NULL;
63 delete h2;
64 }
65
recursive_delete(heptagon * h,int i)66 void recursive_delete(heptagon *h, int i) {
67 heptagon *h2 = h->move(i);
68 { for(int i=1; i<S7; i++)
69 if(h2->move(i) && h2->move(i)->move(0) == h2)
70 recursive_delete(h2, i); }
71 if(h2->alt && h2->alt->alt == h2->alt) {
72 DEBB(DF_MEMORY, ("destroying alternate map ", h2->alt));
73 for(hrmap *& hm: allmaps) {
74 if(hm->getOrigin() == h2->alt) {
75 delete hm;
76 hm = allmaps.back();
77 allmaps.pop_back();
78 DEBB(DF_MEMORY, ("map found (", isize(allmaps), " altmaps total)"));
79 break;
80 }
81 }
82 }
83 if(h2->alt) {
84 h2->alt->cdata = NULL;
85 }
86 delete_heptagon(h2);
87 h->move(i) = NULL;
88 }
89
unsafeLand(cell * c)90 bool unsafeLand(cell *c) {
91 return
92 isCyclic(c->land) || isGravityLand(c->land) || isHaunted(c->land) ||
93 among(c->land, laCaribbean, laOcean, laGraveyard, laPrincessQuest);
94 }
95
save_memory()96 EX void save_memory() {
97 if(quotient || !hyperbolic || NONSTDVAR) return;
98 if(!memory_saving_mode) return;
99 if(unsafeLand(cwt.at)) return;
100 int d = celldist(cwt.at);
101 if(d < LIM+10) return;
102
103 heptagon *at = cwt.at->master;
104 heptagon *orig = currentmap->gamestart()->master;
105
106 if(recallCell.at) {
107 if(unsafeLand(recallCell.at)) return;
108 heptagon *at2 = recallCell.at->master;
109 int t = 0;
110 while(at != at2) {
111 t++; if(t > 10000) return;
112 if(celldist(at->c7) > celldist(at2->c7))
113 at = at->move(0);
114 else
115 at2 = at2->move(0);
116 }
117 }
118
119 while(celldist(at->c7) > d-LIM) at = at->move(0);
120
121 // go back to such a point X that all the heptagons adjacent to the current 'at'
122 // are the children of X. This X becomes the new 'at'
123 if(true) {
124 heptagon *allh[9];
125 int hcount = 0;
126 allh[hcount++] = at;
127 for(int j=0; j<S7; j++)
128 if(allh[0]->move(j))
129 allh[hcount++] = at->move(j);
130
131 int deuniq_steps = 0;
132
133 int i = 1;
134 while(i < hcount) {
135 if(allh[i] == allh[0])
136 allh[i] = allh[hcount-1], hcount--;
137 else if(celldist(allh[i]->c7) > celldist(allh[0]->c7))
138 allh[i] = allh[i]->move(0);
139 else {
140 if(allh[0] == orig) return;
141 allh[0] = allh[0]->move(0);
142 i = 1;
143 deuniq_steps++;
144 if(deuniq_steps == 10) return;
145 }
146 }
147
148 at = allh[0];
149 }
150
151 if(last_cleared && celldist(at->c7) < celldist(last_cleared->c7))
152 return;
153
154 DEBB(DF_MEMORY, ("celldist = ", make_pair(celldist(cwt.at), celldist(at->c7))));
155
156 heptagon *at1 = at;
157 while(at != last_cleared && at != orig) {
158 heptagon *atn = at;
159 at = at->move(0);
160
161 for(int i=1; i<S7; i++)
162 if(at->move(i) && at->move(i) != atn)
163 recursive_delete(at, i);
164 }
165
166 last_cleared = at1;
167 DEBB(DF_MEMORY, ("current cellcount = ", cellcount));
168
169 sort(removed_cells.begin(), removed_cells.end());
170 callhooks(hooks_removecells);
171 removed_cells.clear();
172 }
173
174 EX purehookset hooks_removecells;
175
is_cell_removed(cell * c)176 EX bool is_cell_removed(cell *c) {
177 return binary_search(removed_cells.begin(), removed_cells.end(), c);
178 }
179
set_if_removed(cell * & c,cell * val)180 EX void set_if_removed(cell*& c, cell *val) {
181 if(is_cell_removed(c)) c = val;
182 }
183
184 typedef array<char, 1048576> reserve_block;
185
186 EX int reserve_count = 0;
187 EX int reserve_limit = 128;
188
189 const int max_reserve = 4096;
190 array<reserve_block*, max_reserve> reserve;
191
192 std::new_handler default_handler;
193
194 EX purehookset hooks_clear_cache;
195
reserve_handler()196 void reserve_handler() {
197 if(reserve_count) {
198 reserve_count--;
199 delete reserve[reserve_count];
200 }
201 if(reserve_count < 32) callhooks(hooks_clear_cache);
202 if(!reserve_count) std::set_new_handler(default_handler);
203 }
204
apply_memory_reserve()205 EX void apply_memory_reserve() {
206 #if CAP_MEMORY_RESERVE
207 if(reserve_count > 0) std::set_new_handler(default_handler);
208 if(reserve_limit > max_reserve) reserve_limit = max_reserve;
209 if(reserve_limit < 0) reserve_limit = 0;
210 while(reserve_count > reserve_limit) { reserve_count--; delete reserve[reserve_count]; }
211 try {
212 while(reserve_count < reserve_limit) {
213 reserve[reserve_count] = new reserve_block;
214 /* only if successful */
215 reserve_count++;
216 }
217 }
218 catch(std::bad_alloc&) {}
219 #if ISWINDOWS
220 // no get_new_handler on this compiler...
221 default_handler = [] { throw std::bad_alloc(); };
222 #else
223 default_handler = std::get_new_handler();
224 #endif
225 if(reserve_count > 0) std::set_new_handler(reserve_handler);
226 #endif
227 }
228
memory_for_lib()229 EX void memory_for_lib() {
230 if(reserve_count) { reserve_count--; delete reserve[reserve_count]; }
231 }
232
show_memory_menu()233 EX void show_memory_menu() {
234 gamescreen(0);
235 dialog::init(XLAT("memory"));
236
237 dialog::addHelp(XLAT(
238 "HyperRogue takes place in a world that is larger than anything Euclidean. "
239 "Unfortunately, in some cases running it on an Euclidean computer might be "
240 "a problem -- the computer could simply run out of memory. Some lands (such as the Ocean or the Brown Islands) "
241 "may use up memory very fast!\n\n"
242 ));
243
244 if(sizeof(void*) <= 4)
245 dialog::addHelp(XLAT(
246 "You are playing a 32-bit HyperRogue executable, which can only use 4GB of memory.\n\n"));
247
248 dialog::addHelp(XLAT(
249 "Although you are extremely unlikely to return to a place you have already been to, "
250 "the game never forgets these areas, unless you start a new game, use an Orb of "
251 "Safety (found in Land of Eternal Motion, the Prairie, and the Ocean), or activate the memory "
252 "saving mode, which tries to intelligently predict which cells you will never find "
253 "again and can be safely forgotten.\n\n")
254 );
255
256 if(cheater) dialog::addSelItem(XLAT("cells in memory"), its(cellcount) + "+" + its(heptacount), 0);
257
258 dialog::addBoolItem(XLAT("memory saving mode"), memory_saving_mode, 'f');
259 dialog::add_action([] { memory_saving_mode = !memory_saving_mode; if(memory_saving_mode) save_memory(), apply_memory_reserve(); });
260
261 dialog::addBoolItem_action(XLAT("show memory warnings"), show_memory_warning, 'w');
262
263 #if CAP_MEMORY_RESERVE
264 if(reserve_limit > 0 && reserve_count < reserve_limit) {
265 dialog::addItem(XLAT("just let me find Orb of Safety or finish the game"), 'l');
266 dialog::add_action([] { ignored_memory_warning = true; popScreen(); });
267 }
268
269 dialog::addSelItem("memory reserve", its(reserve_count) + "/" + its(reserve_limit) + " MB", 'r');
270 dialog::add_action([] {
271 dialog::editNumber(reserve_limit, 0, max_reserve, 16, 128, XLAT("memory reserve"),
272 XLAT("When to show a memory warning.")
273 );
274 dialog::bound_low(0);
275 dialog::bound_up(max_reserve);
276 dialog::reaction = apply_memory_reserve;
277 });
278 #endif
279
280 dialog::addItem(XLAT("clear caches"), 'c');
281 dialog::add_action([] { callhooks(hooks_clear_cache); });
282
283 dialog::addBack();
284 dialog::display();
285 }
286
protect_memory()287 EX bool protect_memory() {
288 #if CAP_MEMORY_RESERVE
289 apply_memory_reserve();
290 if(reserve_limit && reserve_count < reserve_limit && !ignored_memory_warning) {
291 pushScreen(show_memory_menu);
292 return true;
293 }
294 if(reserve_limit && reserve_count < 8) {
295 pushScreen(show_memory_menu);
296 return true;
297 }
298 #endif
299 return false;
300 }
301
memory_issues()302 EX bool memory_issues() {
303 return reserve_limit && reserve_count < 16;
304 }
305
306 }
307