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