Fix EmergeThread hang on exit
[oweals/minetest.git] / src / emerge.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20
21 #include "server.h"
22 #include <iostream>
23 #include <queue>
24 #include "clientserver.h"
25 #include "map.h"
26 #include "jmutexautolock.h"
27 #include "main.h"
28 #include "constants.h"
29 #include "voxel.h"
30 #include "config.h"
31 #include "mapblock.h"
32 #include "serverobject.h"
33 #include "settings.h"
34 #include "script.h"
35 #include "scriptapi.h"
36 #include "profiler.h"
37 #include "log.h"
38 #include "nodedef.h"
39 #include "biome.h"
40 #include "emerge.h"
41 #include "mapgen_v6.h"
42
43
44 EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) {
45         //register built-in mapgens
46         registerMapgen("v6", new MapgenFactoryV6());
47
48         this->biomedef = bdef ? bdef : new BiomeDefManager(gamedef);
49         this->params   = NULL;
50         this->mapgen   = NULL;
51         
52         queuemutex.Init();
53         emergethread = new EmergeThread((Server *)gamedef);
54 }
55
56
57 EmergeManager::~EmergeManager() {
58         emergethread->setRun(false);
59         emergethread->qevent.signal();
60         emergethread->stop();
61         
62         delete emergethread;
63         delete biomedef;
64         delete mapgen;
65         delete params;
66 }
67
68
69 void EmergeManager::initMapgens(MapgenParams *mgparams) {
70         if (mapgen)
71                 return;
72         
73         this->params = mgparams;
74         this->mapgen = getMapgen(); //only one mapgen for now!
75 }
76
77
78 Mapgen *EmergeManager::getMapgen() {
79         if (!mapgen) {
80                 mapgen = createMapgen(params->mg_name, 0, params, this);
81                 if (!mapgen) {
82                         infostream << "EmergeManager: falling back to mapgen v6" << std::endl;
83                         delete params;
84                         params = createMapgenParams("v6");
85                         mapgen = createMapgen("v6", 0, params, this);
86                 }
87         }
88         return mapgen;
89 }
90
91
92 bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) {
93         std::map<v3s16, BlockEmergeData *>::const_iterator iter;
94         BlockEmergeData *bedata;
95         u16 count;
96         u8 flags = 0;
97         
98         if (allow_generate)
99                 flags |= BLOCK_EMERGE_ALLOWGEN;
100
101         //TODO:
102         // add logic to select which emergethread to add it to
103         //  - one with the least queue contents?
104         //  - if a queue is too full, move onto another one
105         //  - use the peer id sometime
106
107         {
108                 JMutexAutoLock queuelock(queuemutex);
109                 
110                 count = blocks_enqueued.size();
111                 u16 queuelimit_total = 256;
112                 if (count >= queuelimit_total)
113                         return false;
114
115                 count = peer_queue_count[peer_id];
116                 u16 queuelimit_peer = allow_generate ? 1 : 5;
117                 if (count >= queuelimit_peer)
118                         return false;
119                 
120                 iter = blocks_enqueued.find(p);
121                 if (iter != blocks_enqueued.end()) {
122                         bedata = iter->second;
123                         bedata->flags |= flags;
124                         return true;
125                 }
126
127                 bedata = new BlockEmergeData;
128                 bedata->flags = flags;
129                 bedata->peer_requested = peer_id;
130                 blocks_enqueued.insert(std::make_pair(p, bedata));
131                 
132                 peer_queue_count[peer_id] = count + 1;
133                 
134                 emergethread->blockqueue.push(p);
135         }
136         emergethread->qevent.signal();
137         
138         return true;
139 }
140
141
142 bool EmergeManager::popBlockEmerge(v3s16 *pos, u8 *flags) {
143         std::map<v3s16, BlockEmergeData *>::iterator iter;
144         JMutexAutoLock queuelock(queuemutex);
145
146         if (emergethread->blockqueue.empty())
147                 return false;
148         v3s16 p = emergethread->blockqueue.front();
149         emergethread->blockqueue.pop();
150         
151         *pos = p;
152         
153         iter = blocks_enqueued.find(p);
154         if (iter == blocks_enqueued.end()) 
155                 return false; //uh oh, queue and map out of sync!!
156
157         BlockEmergeData *bedata = iter->second;
158         *flags = bedata->flags;
159         
160         peer_queue_count[bedata->peer_requested]--;
161
162         delete bedata;
163         blocks_enqueued.erase(iter);
164         
165         return true;
166 }
167
168
169 int EmergeManager::getGroundLevelAtPoint(v2s16 p) {
170         if (!mapgen)
171                 return 0;
172         return mapgen->getGroundLevelAtPoint(p);
173 }
174
175
176 bool EmergeManager::isBlockUnderground(v3s16 blockpos) {
177         /*
178         v2s16 p = v2s16((blockpos.X * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2,
179                                         (blockpos.Y * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2);
180         int ground_level = getGroundLevelAtPoint(p);
181         return blockpos.Y * (MAP_BLOCKSIZE + 1) <= min(water_level, ground_level);
182         */
183
184         //yuck, but then again, should i bother being accurate?
185         //the height of the nodes in a single block is quite variable
186         return blockpos.Y * (MAP_BLOCKSIZE + 1) <= params->water_level;
187 }
188
189
190 u32 EmergeManager::getBlockSeed(v3s16 p) {
191         return (u32)(params->seed & 0xFFFFFFFF) +
192                 p.Z * 38134234 +
193                 p.Y * 42123 +
194                 p.Y * 23;
195 }
196
197
198 Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid,
199                                                                         MapgenParams *mgparams, EmergeManager *emerge) {
200         std::map<std::string, MapgenFactory *>::const_iterator iter = mglist.find(mgname);
201         if (iter == mglist.end()) {
202                 errorstream << "EmergeManager; mapgen " << mgname <<
203                  " not registered" << std::endl;
204                 return NULL;
205         }
206         
207         MapgenFactory *mgfactory = iter->second;
208         return mgfactory->createMapgen(mgid, mgparams, emerge);
209 }
210
211
212 MapgenParams *EmergeManager::createMapgenParams(std::string mgname) {
213         std::map<std::string, MapgenFactory *>::const_iterator iter = mglist.find(mgname);
214         if (iter == mglist.end()) {
215                 errorstream << "EmergeManager: mapgen " << mgname <<
216                  " not registered" << std::endl;
217                 return NULL;
218         }
219         
220         MapgenFactory *mgfactory = iter->second;
221         return mgfactory->createMapgenParams();
222 }
223
224
225 MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) {
226         std::string mg_name = settings->get("mg_name");
227         MapgenParams *mgparams = createMapgenParams(mg_name);
228         
229         mgparams->mg_name     = mg_name;
230         mgparams->seed        = settings->getU64(settings == g_settings ? "fixed_map_seed" : "seed");
231         mgparams->water_level = settings->getS16("water_level");
232         mgparams->chunksize   = settings->getS16("chunksize");
233         mgparams->flags       = settings->getS32("mg_flags");
234
235         if (!mgparams->readParams(settings)) {
236                 delete mgparams;
237                 return NULL;
238         }
239         return mgparams;
240 }
241
242
243 void EmergeManager::setParamsToSettings(Settings *settings) {
244         settings->set("mg_name",         params->mg_name);
245         settings->setU64("seed",         params->seed);
246         settings->setS16("water_level",  params->water_level);
247         settings->setS16("chunksize",    params->chunksize);
248         settings->setFlagStr("mg_flags", params->flags, flagdesc_mapgen);
249
250         params->writeParams(settings);
251 }
252
253
254 bool EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) {
255         mglist.insert(std::make_pair(mgname, mgfactory));
256         infostream << "EmergeManager: registered mapgen " << mgname << std::endl;
257 }
258
259
260
261 class MapEditEventIgnorer
262 {
263 public:
264         MapEditEventIgnorer(bool *flag):
265                 m_flag(flag)
266         {
267                 if(*m_flag == false)
268                         *m_flag = true;
269                 else
270                         m_flag = NULL;
271         }
272
273         ~MapEditEventIgnorer()
274         {
275                 if(m_flag)
276                 {
277                         assert(*m_flag);
278                         *m_flag = false;
279                 }
280         }
281
282 private:
283         bool *m_flag;
284 };
285
286 class MapEditEventAreaIgnorer
287 {
288 public:
289         MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a):
290                 m_ignorevariable(ignorevariable)
291         {
292                 if(m_ignorevariable->getVolume() == 0)
293                         *m_ignorevariable = a;
294                 else
295                         m_ignorevariable = NULL;
296         }
297
298         ~MapEditEventAreaIgnorer()
299         {
300                 if(m_ignorevariable)
301                 {
302                         assert(m_ignorevariable->getVolume() != 0);
303                         *m_ignorevariable = VoxelArea();
304                 }
305         }
306
307 private:
308         VoxelArea *m_ignorevariable;
309 };
310
311
312 #if 1
313
314 #define EMERGE_DBG_OUT(x) \
315         { if (enable_mapgen_debug_info) \
316         infostream << "EmergeThread: " x << std::endl; }
317
318 bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, 
319                                                                         BlockMakeData *data, bool allow_gen) {
320         v2s16 p2d(p.X, p.Z);
321         //envlock: usually takes <=1ms, sometimes 90ms or ~400ms to acquire
322         JMutexAutoLock envlock(m_server->m_env_mutex); 
323         
324         // Load sector if it isn't loaded
325         if (map->getSectorNoGenerateNoEx(p2d) == NULL)
326                 map->loadSectorMeta(p2d);
327
328         // Attempt to load block
329         MapBlock *block = map->getBlockNoCreateNoEx(p);
330         if (!block || block->isDummy() || !block->isGenerated()) {
331                 EMERGE_DBG_OUT("not in memory, attempting to load from disk");
332                 block = map->loadBlock(p);
333         }
334
335         // If could not load and allowed to generate,
336         // start generation inside this same envlock
337         if (allow_gen && (block == NULL || !block->isGenerated())) {
338                 EMERGE_DBG_OUT("generating");
339                 map->initBlockMake(data, p);
340                 return true;
341         }
342         
343         *b = block;
344         return false;
345 }
346
347
348 void *EmergeThread::Thread() {
349         ThreadStarted();
350         log_register_thread("EmergeThread");
351         DSTACK(__FUNCTION_NAME);
352         BEGIN_DEBUG_EXCEPTION_HANDLER
353
354         v3s16 last_tried_pos(-32768,-32768,-32768); // For error output
355         v3s16 p;
356         u8 flags;
357         
358         map    = (ServerMap *)&(m_server->m_env->getMap());
359         emerge = m_server->m_emerge;
360         mapgen = emerge->getMapgen();
361         
362         while (getRun())
363         try {
364                 while (!emerge->popBlockEmerge(&p, &flags)) {
365                         qevent.wait();
366                         if (!getRun())
367                                 goto exit_emerge_loop;
368                 }
369
370                 last_tried_pos = p;
371                 if (blockpos_over_limit(p))
372                         continue;
373
374                 bool allow_generate = flags & BLOCK_EMERGE_ALLOWGEN;
375                 EMERGE_DBG_OUT("p=" PP(p) " allow_generate=" << allow_generate);
376                 
377                 /*
378                         Try to fetch block from memory or disk.
379                         If not found and asked to generate, initialize generator.
380                 */
381                 BlockMakeData data;
382                 MapBlock *block = NULL;
383                 core::map<v3s16, MapBlock *> modified_blocks;
384                 
385                 if (getBlockOrStartGen(p, &block, &data, allow_generate)) {
386                         {
387                                 ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG);
388                                 TimeTaker t("mapgen::make_block()");
389
390                                 mapgen->makeChunk(&data);
391
392                                 if (enable_mapgen_debug_info == false)
393                                         t.stop(true); // Hide output
394                         }
395
396                         {
397                                 //envlock: usually 0ms, but can take either 30 or 400ms to acquire
398                                 JMutexAutoLock envlock(m_server->m_env_mutex); 
399                                 ScopeProfiler sp(g_profiler, "EmergeThread: after "
400                                                 "Mapgen::makeChunk (envlock)", SPT_AVG);
401
402                                 map->finishBlockMake(&data, modified_blocks);
403                                 
404                                 block = map->getBlockNoCreateNoEx(p);
405                                 if (block) {
406                                         /*
407                                                 Do some post-generate stuff
408                                         */
409                                         v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE;
410                                         v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE +
411                                                                  v3s16(1,1,1) * (MAP_BLOCKSIZE - 1);
412
413                                         // Ignore map edit events, they will not need to be sent
414                                         // to anybody because the block hasn't been sent to anybody
415                                         MapEditEventAreaIgnorer 
416                                                 ign(&m_server->m_ignore_map_edit_events_area,
417                                                 VoxelArea(minp, maxp));
418                                         {  // takes about 90ms with -O1 on an e3-1230v2
419                                                 scriptapi_environment_on_generated(m_server->m_lua,
420                                                                 minp, maxp, emerge->getBlockSeed(minp));
421                                         }
422
423                                         EMERGE_DBG_OUT("ended up with: " << analyze_block(block));
424                                         
425                                         m_server->m_env->activateBlock(block, 0);
426                                 }
427                         }
428                 }
429
430                 /*
431                         Set sent status of modified blocks on clients
432                 */
433
434                 // NOTE: Server's clients are also behind the connection mutex
435                 //conlock: consistently takes 30-40ms to acquire
436                 JMutexAutoLock lock(m_server->m_con_mutex);
437                 // Add the originally fetched block to the modified list
438                 if (block)
439                         modified_blocks.insert(p, block);
440
441                 // Set the modified blocks unsent for all the clients
442                 for (core::map<u16, RemoteClient*>::Iterator
443                          i = m_server->m_clients.getIterator();
444                          i.atEnd() == false; i++) {
445                         RemoteClient *client = i.getNode()->getValue();
446                         if (modified_blocks.size() > 0) {
447                                 // Remove block from sent history
448                                 client->SetBlocksNotSent(modified_blocks);
449                         }
450                 }
451         }
452         catch (VersionMismatchException &e) {
453                 std::ostringstream err;
454                 err << "World data version mismatch in MapBlock "<<PP(last_tried_pos)<<std::endl;
455                 err << "----"<<std::endl;
456                 err << "\""<<e.what()<<"\""<<std::endl;
457                 err << "See debug.txt."<<std::endl;
458                 err << "World probably saved by a newer version of Minetest."<<std::endl;
459                 m_server->setAsyncFatalError(err.str());
460         }
461         catch (SerializationError &e) {
462                 std::ostringstream err;
463                 err << "Invalid data in MapBlock "<<PP(last_tried_pos)<<std::endl;
464                 err << "----"<<std::endl;
465                 err << "\""<<e.what()<<"\""<<std::endl;
466                 err << "See debug.txt."<<std::endl;
467                 err << "You can ignore this using [ignore_world_load_errors = true]."<<std::endl;
468                 m_server->setAsyncFatalError(err.str());
469         }
470         
471         END_DEBUG_EXCEPTION_HANDLER(errorstream)
472 exit_emerge_loop:
473         log_deregister_thread();
474         return NULL;
475 }
476
477 #else
478
479 void *EmergeThread::Thread() {
480         ThreadStarted();
481         log_register_thread("EmergeThread");
482         DSTACK(__FUNCTION_NAME);
483         BEGIN_DEBUG_EXCEPTION_HANDLER
484
485         bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
486
487         v3s16 last_tried_pos(-32768,-32768,-32768); // For error output
488         ServerMap &map = ((ServerMap&)m_server->m_env->getMap());
489         EmergeManager *emerge = m_server->m_emerge;
490         Mapgen *mapgen = emerge->getMapgen();
491
492         while(getRun())
493         try {
494                 QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop();
495                 if(qptr == NULL)
496                         break;
497                 SharedPtr<QueuedBlockEmerge> q(qptr);
498
499                 v3s16 &p = q->pos;
500                 v2s16 p2d(p.X,p.Z);
501
502                 last_tried_pos = p;
503
504                 /*
505                         Do not generate over-limit
506                 */
507                 if (blockpos_over_limit(p))
508                         continue;
509
510                 //infostream<<"EmergeThread::Thread(): running"<<std::endl;
511
512                 //TimeTaker timer("block emerge");
513
514                 /*
515                         Try to emerge it from somewhere.
516
517                         If it is only wanted as optional, only loading from disk
518                         will be allowed.
519                 */
520
521                 /*
522                         Check if any peer wants it as non-optional. In that case it
523                         will be generated.
524
525                         Also decrement the emerge queue count in clients.
526                 */
527
528                 bool only_from_disk = true;
529                 {
530                         core::map<u16, u8>::Iterator i;
531                         for (i=q->s.getIterator(); !i.atEnd(); i++) {
532                                 u8 flags = i.getNode()->getValue();
533                                 if (!(flags & BLOCK_EMERGE_FLAG_FROMDISK)) {
534                                         only_from_disk = false;
535                                         break;
536                                 }
537                         }
538                 }
539
540                 if (enable_mapgen_debug_info)
541                         infostream<<"EmergeThread: p="
542                                         <<"("<<p.X<<","<<p.Y<<","<<p.Z<<") "
543                                         <<"only_from_disk="<<only_from_disk<<std::endl;
544                                         
545                 MapBlock *block = NULL;
546                 bool got_block = true;
547                 core::map<v3s16, MapBlock*> modified_blocks;
548
549                 /*
550                         Try to fetch block from memory or disk.
551                         If not found and asked to generate, initialize generator.
552                 */
553
554                 bool started_generate = false;
555                 BlockMakeData data;
556                 {
557                         JMutexAutoLock envlock(m_server->m_env_mutex);
558                         
559                         // Load sector if it isn't loaded
560                         if(map.getSectorNoGenerateNoEx(p2d) == NULL)
561                                 map.loadSectorMeta(p2d);
562
563                         // Attempt to load block
564                         block = map.getBlockNoCreateNoEx(p);
565                         if(!block || block->isDummy() || !block->isGenerated()) {
566                                 if(enable_mapgen_debug_info)
567                                         infostream<<"EmergeThread: not in memory, "
568                                                         <<"attempting to load from disk"<<std::endl;
569
570                                 block = map.loadBlock(p);
571                         }
572
573                         // If could not load and allowed to generate, start generation
574                         // inside this same envlock
575                         if(only_from_disk == false &&
576                                         (block == NULL || block->isGenerated() == false)){
577                                 if(enable_mapgen_debug_info)
578                                         infostream<<"EmergeThread: generating"<<std::endl;
579                                 started_generate = true;
580
581                                 map.initBlockMake(&data, p);
582                         }
583                 }
584
585                 /*
586                         If generator was initialized, generate now when envlock is free.
587                 */
588                 if(started_generate) {
589                         {
590                                 ScopeProfiler sp(g_profiler, "EmergeThread: mapgen::make_block",
591                                                 SPT_AVG);
592                                 TimeTaker t("mapgen::make_block()");
593
594                                 mapgen->makeChunk(&data);
595
596                                 if (enable_mapgen_debug_info == false)
597                                         t.stop(true); // Hide output
598                         }
599
600                         do{ // enable break
601                                 // Lock environment again to access the map
602                                 JMutexAutoLock envlock(m_server->m_env_mutex);
603
604                                 ScopeProfiler sp(g_profiler, "EmergeThread: after "
605                                                 "mapgen::make_block (envlock)", SPT_AVG);
606
607                                 // Blit data back on map, update lighting, add mobs and
608                                 // whatever this does
609                                 map.finishBlockMake(&data, modified_blocks);
610                                 
611                                 // Get central block
612                                 block = map.getBlockNoCreateNoEx(p);
613
614                                 // If block doesn't exist, don't try doing anything with it
615                                 // This happens if the block is not in generation boundaries
616                                 if(!block)
617                                         break;
618
619                                 /*
620                                         Do some post-generate stuff
621                                 */
622                                 v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE;
623                                 v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE +
624                                                 v3s16(1,1,1) * (MAP_BLOCKSIZE - 1);
625
626                                 /*
627                                         Ignore map edit events, they will not need to be
628                                         sent to anybody because the block hasn't been sent
629                                         to anybody
630                                 */
631                                 MapEditEventAreaIgnorer ign(
632                                                 &m_server->m_ignore_map_edit_events_area,
633                                                 VoxelArea(minp, maxp));
634                                 {
635                                         TimeTaker timer("on_generated");
636                                         scriptapi_environment_on_generated(m_server->m_lua,
637                                                         minp, maxp, emerge->getBlockSeed(minp));
638                                         //int t = timer.stop(true);
639                                         //dstream<<"on_generated took "<<t<<"ms"<<std::endl;
640                                 }
641
642                                 if (enable_mapgen_debug_info)
643                                         infostream << "EmergeThread: ended up with: "
644                                                         << analyze_block(block) << std::endl;
645
646                                 // Activate objects and stuff
647                                 m_server->m_env->activateBlock(block, 0);
648                         }while(false);
649                 }
650
651                 if(block == NULL)
652                         got_block = false;
653
654                 /*
655                         Set sent status of modified blocks on clients
656                 */
657
658                 // NOTE: Server's clients are also behind the connection mutex
659                 JMutexAutoLock lock(m_server->m_con_mutex);
660
661                 /*
662                         Add the originally fetched block to the modified list
663                 */
664                 if(got_block)
665                         modified_blocks.insert(p, block);
666
667                 /*
668                         Set the modified blocks unsent for all the clients
669                 */
670                 for(core::map<u16, RemoteClient*>::Iterator
671                                 i = m_server->m_clients.getIterator();
672                                 i.atEnd() == false; i++) {
673                         RemoteClient *client = i.getNode()->getValue();
674                         if(modified_blocks.size() > 0) {
675                                 // Remove block from sent history
676                                 client->SetBlocksNotSent(modified_blocks);
677                         }
678                 }
679                                                         
680
681 niters++;
682         }
683         catch (VersionMismatchException &e) {
684                 std::ostringstream err;
685                 err << "World data version mismatch in MapBlock "<<PP(last_tried_pos)<<std::endl;
686                 err << "----"<<std::endl;
687                 err << "\""<<e.what()<<"\""<<std::endl;
688                 err << "See debug.txt."<<std::endl;
689                 err << "World probably saved by a newer version of Minetest."<<std::endl;
690                 m_server->setAsyncFatalError(err.str());
691         }
692         catch (SerializationError &e) {
693                 std::ostringstream err;
694                 err << "Invalid data in MapBlock "<<PP(last_tried_pos)<<std::endl;
695                 err << "----"<<std::endl;
696                 err << "\""<<e.what()<<"\""<<std::endl;
697                 err << "See debug.txt."<<std::endl;
698                 err << "You can ignore this using [ignore_world_load_errors = true]."<<std::endl;
699                 m_server->setAsyncFatalError(err.str());
700         }
701 printf("emergethread iterated %d times\n", niters);
702         END_DEBUG_EXCEPTION_HANDLER(errorstream)
703         log_deregister_thread();
704         return NULL;
705 }
706
707 #endif