• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 17470746511

04 Sep 2025 04:40PM UTC coverage: 59.805% (-0.02%) from 59.821%
17470746511

push

github

web-flow
Default script priority changed (#806)

* profile cleanup, time measurement for script scheduler

* missing file

* profiling npc template search makes I think no longer sense

* renamed variable

* fixed namespace

* typo

* maybe now?

* scheduler != tasks ...

* use correct macro

* convert to double

* fixed runlist stats, correctly log delay

* reset statistics each minute

* log standard deviation instead of variance

* mean instruction time

* pol.cfg "DefaultPriority" (default 1)

* changed the correct file and removed leftover files

* reduced output

* changed default prio to 10 and added docs

* online docs

* fixed warning

* removed more leftovers

48 of 68 new or added lines in 9 files covered. (70.59%)

8 existing lines in 4 files now uncovered.

43678 of 73034 relevant lines covered (59.81%)

427804.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.27
/pol-core/pol/globals/script_internals.cpp
1
#include "script_internals.h"
2

3
#include <iterator>
4
#include <string.h>
5

6
#include "../../clib/logfacility.h"
7
#include "../../clib/passert.h"
8
#include "../../clib/stlutil.h"
9
#include "../../plib/systemstate.h"
10
#include "../polsig.h"
11
#include "../uoexec.h"
12
#include "state.h"
13

14
namespace Pol
15
{
16
namespace Core
17
{
18
ScriptScheduler scriptScheduler;
19

20
// This number is intended so that PID and custom GUMPIDS will never clash together
21
// and to avoid breaking the old assumption that gumpid == pid when gumpid has been
22
// automatically generated (for backward compatibility).
23
// Custom gumpids must always be < PID_MIN.
24
const unsigned int ScriptScheduler::PID_MIN = 0x01000000;
25

26
ScriptScheduler::ScriptScheduler()
3✔
27
    : priority_divide( 1 ),
3✔
28
      scrstore(),
3✔
29
      runlist(),
3✔
30
      ranlist(),
3✔
31
      holdlist(),
3✔
32
      notimeoutholdlist(),
3✔
33
      debuggerholdlist(),
3✔
34
      pidlist(),
3✔
35
      next_pid( PID_MIN )
3✔
36
{
37
}
3✔
38

39
ScriptScheduler::~ScriptScheduler() {}
3✔
40

41
// Note, when the program exits, each executor in these queues
42
// will be deleted by cleanup_scripts()
43
// Therefore, any object that owns an executor must be destroyed
44
// before cleanup_scripts() is called.
45
void ScriptScheduler::deinitialize()
3✔
46
{
47
  scrstore.clear();
3✔
48
  Clib::delete_all( runlist );
3✔
49
  while ( !holdlist.empty() )
9✔
50
  {
51
    delete ( ( *holdlist.begin() ).second );
6✔
52
    holdlist.erase( holdlist.begin() );
6✔
53
  }
54
  while ( !notimeoutholdlist.empty() )
3✔
55
  {
56
    delete ( *notimeoutholdlist.begin() );
×
57
    notimeoutholdlist.erase( notimeoutholdlist.begin() );
×
58
  }
59
  while ( !debuggerholdlist.empty() )
3✔
60
  {
61
    delete ( *debuggerholdlist.begin() );
×
62
    debuggerholdlist.erase( debuggerholdlist.begin() );
×
63
  }
64
}
3✔
65

66
ScriptScheduler::Memory ScriptScheduler::estimateSize( bool verbose ) const
2✔
67
{
68
  Memory usage;
69
  memset( &usage, 0, sizeof( usage ) );
2✔
70

71
  usage.script_size = sizeof( int )            /*priority_divide*/
2✔
72
                      + sizeof( unsigned int ) /*next_pid*/
73
                      + Clib::memsize( pidlist );
2✔
74
  usage.scriptstorage_size = Clib::memsize( scrstore );
2✔
75
  for ( const auto& script : scrstore )
164✔
76
  {
77
    usage.scriptstorage_size += script.first.capacity();
162✔
78
    if ( script.second.get() != nullptr )
162✔
79
      usage.scriptstorage_size += script.second->sizeEstimate();
162✔
80
  }
81
  usage.scriptstorage_count = scrstore.size();
2✔
82

83
  std::string verbose_w;
2✔
84
  if ( verbose )
2✔
85
    verbose_w = GET_LOG_FILESTAMP + "\n";
1✔
86
  usage.script_size += Clib::memsize( runlist );
2✔
87
  if ( verbose )
2✔
88
    verbose_w += "runlist:\n";
1✔
89
  for ( const auto& exec : runlist )
2✔
90
  {
UNCOV
91
    if ( exec != nullptr )
×
92
    {
UNCOV
93
      usage.script_size += exec->sizeEstimate();
×
UNCOV
94
      if ( verbose )
×
UNCOV
95
        fmt::format_to( std::back_inserter( verbose_w ), "{} {} \n", exec->scriptname(),
×
UNCOV
96
                        exec->sizeEstimate() );
×
97
    }
98
  }
99
  usage.script_count += runlist.size();
2✔
100

101
  usage.script_size += Clib::memsize( ranlist );
2✔
102
  if ( verbose )
2✔
103
    verbose_w += "ranlist:\n";
1✔
104
  for ( const auto& exec : ranlist )
2✔
105
  {
106
    if ( exec != nullptr )
×
107
    {
108
      usage.script_size += exec->sizeEstimate();
×
109
      if ( verbose )
×
110
        fmt::format_to( std::back_inserter( verbose_w ), "{} {}\n", exec->scriptname(),
×
111
                        exec->sizeEstimate() );
×
112
    }
113
  }
114
  usage.script_count += ranlist.size();
2✔
115

116
  if ( verbose )
2✔
117
    verbose_w += "holdlist:\n";
1✔
118
  usage.script_size += Clib::memsize( holdlist );
2✔
119
  for ( const auto& hold : holdlist )
8✔
120
  {
121
    if ( hold.second != nullptr )
6✔
122
    {
123
      usage.script_size += hold.second->sizeEstimate();
6✔
124
      if ( verbose )
6✔
125
        fmt::format_to( std::back_inserter( verbose_w ), "{} {}\n", hold.second->scriptname(),
3✔
126
                        hold.second->sizeEstimate() );
6✔
127
    }
128
  }
129
  usage.script_count += holdlist.size();
2✔
130

131
  usage.script_size += Clib::memsize( notimeoutholdlist );
2✔
132
  if ( verbose )
2✔
133
    verbose_w += "notimeoutholdlist:\n";
1✔
134
  for ( const auto& hold : notimeoutholdlist )
4✔
135
  {
136
    if ( hold != nullptr )
2✔
137
    {
138
      usage.script_size += hold->sizeEstimate();
2✔
139
      if ( verbose )
2✔
140
        fmt::format_to( std::back_inserter( verbose_w ), "{} {}\n", hold->scriptname(),
1✔
141
                        hold->sizeEstimate() );
2✔
142
    }
143
  }
144
  usage.script_count += notimeoutholdlist.size();
2✔
145

146
  usage.script_size += Clib::memsize( debuggerholdlist );
2✔
147
  if ( verbose )
2✔
148
    verbose_w += "debuggerholdlist:\n";
1✔
149
  for ( const auto& hold : debuggerholdlist )
2✔
150
  {
151
    if ( hold != nullptr )
×
152
    {
153
      usage.script_size += hold->sizeEstimate();
×
154
      if ( verbose )
×
155
        fmt::format_to( std::back_inserter( verbose_w ), "{} {}\n", hold->scriptname(),
×
156
                        hold->sizeEstimate() );
×
157
    }
158
  }
159
  usage.script_count += debuggerholdlist.size();
2✔
160
  if ( verbose )
2✔
161
  {
162
    auto log = OPEN_FLEXLOG( "log/memoryusagescripts.log", false );
1✔
163
    FLEXLOGLN( log, verbose_w );  // extra newline at the end,seperates the old from the new entry
1✔
164

165
    CLOSE_FLEXLOG( log );
1✔
166
  }
1✔
167

168
  return usage;
4✔
169
}
2✔
170

171

172
// This uses 2 deque, runlist are the current active scripts
173
// which get directly removed and depending if they are finished or not added to ranlist
174
// at the very end ranlist gets swapped with runlist for the next run
175
// Hours wasted: 4
176
// * single std::list is faster when removing, but readding sleeping scripts is slower since new
177
// nodes are created on the heap instead of using the capacity like other containers
178
// * single std::deque and only remove inactive scripts, slower since it needs to reorder due to
179
// removing entries inbetween
180
//
181
// Thus as of now even if it sounds wrong having 2 std::deque is the fastest option.
182
// Removing at the front and adding at the end is fast especially since it does not have to allocate
183
// new memory since it uses the internal capacity.
184
void ScriptScheduler::run_ready()
27,538,287✔
185
{
186
  THREAD_CHECKPOINT( scripts, 110 );
27,538,287✔
187
  while ( !runlist.empty() )
55,119,639✔
188
  {
189
    ExecList::iterator itr = runlist.begin();
27,581,352✔
190
    UOExecutor* ex = *itr;
27,581,352✔
191
    passert_paranoid( ex != nullptr );
192
    runlist.pop_front();  // remove it directly, since itr can get invalid during execution
27,581,352✔
193

194
    Clib::scripts_thread_script = ex->scriptname();
27,581,352✔
195

196
    int inscount = 0;
27,581,352✔
197
    int totcount = 0;
27,581,352✔
198
    int insleft = ex->priority() / priority_divide;
27,581,352✔
199
    if ( insleft == 0 )
27,581,352✔
200
      insleft = 1;
×
201

202
    THREAD_CHECKPOINT( scripts, 111 );
27,581,352✔
203

204
    while ( ex->runnable() )
110,573,435✔
205
    {
206
      ++ex->instr_cycles;
110,572,539✔
207
      THREAD_CHECKPOINT( scripts, 112 );
110,572,539✔
208
      Clib::scripts_thread_scriptPC = ex->PC;
110,572,539✔
209
      ex->execInstr();
110,572,539✔
210

211
      THREAD_CHECKPOINT( scripts, 113 );
110,572,539✔
212

213
      if ( ex->blocked() )
110,572,539✔
214
      {
215
        ex->warn_runaway_on_cycle =
27,539,932✔
216
            ex->instr_cycles + Plib::systemstate.config.runaway_script_threshold;
27,539,932✔
217
        ex->runaway_cycles = 0;
27,539,932✔
218
        break;
27,539,932✔
219
      }
220

221
      if ( ex->instr_cycles == ex->warn_runaway_on_cycle )
83,032,607✔
222
      {
223
        ex->runaway_cycles += Plib::systemstate.config.runaway_script_threshold;
24✔
224
        if ( ex->warn_on_runaway() )
24✔
225
        {
226
          std::string tmp = fmt::format( "Runaway script[{}]: ({} cycles)\n", ex->pid(),
×
227
                                         ex->scriptname(), ex->runaway_cycles );
24✔
228
          ex->show_context( tmp, ex->PC );
24✔
229
          SCRIPTLOG( tmp );
24✔
230
        }
24✔
231
        ex->warn_runaway_on_cycle += Plib::systemstate.config.runaway_script_threshold;
24✔
232
      }
233

234
      if ( ex->critical() )
83,032,607✔
235
      {
236
        ++inscount;
119✔
237
        ++totcount;
119✔
238
        if ( inscount > 1000 )
119✔
239
        {
240
          inscount = 0;
×
241
          if ( Plib::systemstate.config.report_critical_scripts )
×
242
          {
243
            std::string tmp = fmt::format( "Critical script {} has run for {} instructions\n",
244
                                           ex->scriptname(), totcount );
×
245
            ex->show_context( tmp, ex->PC );
×
246
            ERROR_PRINT( tmp );
×
247
          }
×
248
        }
249
        continue;
119✔
250
      }
119✔
251

252
      if ( !--insleft )
83,032,488✔
253
      {
254
        break;
40,524✔
255
      }
256
    }
257

258
    // hmm, this new terminology (runnable()) is confusing
259
    // in this case.  Technically, something that is blocked
260
    // isn't runnable.
261
    if ( !ex->runnable() )
27,581,352✔
262
    {
263
      if ( ex->error() || ex->done )
933✔
264
      {
265
        THREAD_CHECKPOINT( scripts, 114 );
924✔
266

267
        if ( ( ex->pParent != nullptr ) && ex->pParent->runnable() )
924✔
268
        {
269
          ranlist.push_back( ex );
376✔
270
          ex->pParent->revive();
376✔
271
        }
272
        else
273
        {
274
          // Check if the script has a child script running
275
          // Set the parent of the child script nullptr to stop crashing when trying to return to
276
          // parent script
277
          if ( ex->pChild != nullptr )
548✔
278
            ex->pChild->pParent = nullptr;
×
279
          if ( !ex->keep_alive() )
548✔
280
          {
281
            delete ex;
187✔
282
          }
283
          else
284
          {
285
            ex->in_hold_list( Core::HoldListType::NOTIMEOUT_LIST );
361✔
286
            notimeoutholdlist.insert( ex );
361✔
287
          }
288
        }
289
        continue;
933✔
290
      }
291
      else if ( !ex->blocked() )
9✔
292
      {
293
        THREAD_CHECKPOINT( scripts, 115 );
9✔
294

295
        ex->in_hold_list( Core::HoldListType::DEBUGGER_LIST );
9✔
296
        debuggerholdlist.insert( ex );
9✔
297
        continue;
9✔
298
      }
299
    }
300

301
    if ( ex->blocked() )
27,580,419✔
302
    {
303
      THREAD_CHECKPOINT( scripts, 116 );
27,539,932✔
304

305
      if ( ex->sleep_until_clock() )
27,539,932✔
306
      {
307
        ex->in_hold_list( Core::HoldListType::TIMEOUT_LIST );
27,539,511✔
308
        ex->hold_itr( holdlist.insert( HoldList::value_type( ex->sleep_until_clock(), ex ) ) );
27,539,511✔
309
      }
310
      else
311
      {
312
        ex->in_hold_list( Core::HoldListType::NOTIMEOUT_LIST );
421✔
313
        notimeoutholdlist.insert( ex );
421✔
314
      }
315

316
      --ex->sleep_cycles;  // it'd get counted twice otherwise
27,539,932✔
317
      INC_PROFILEVAR_BY( sleep_cycles, -1 );
27,539,932✔
318

319
      THREAD_CHECKPOINT( scripts, 117 );
27,539,932✔
320
    }
321
    else
322
    {
323
      ranlist.push_back( ex );
40,487✔
324
    }
325
  }
326
  THREAD_CHECKPOINT( scripts, 118 );
27,538,287✔
327

328
  runlist.swap( ranlist );
27,538,287✔
329
  THREAD_CHECKPOINT( scripts, 119 );
27,538,287✔
330
}
27,538,287✔
331

332
void ScriptScheduler::schedule( UOExecutor* exec )
197✔
333
{
334
  exec->setDebugLevel( Bscript::Executor::NONE );
197✔
335
  enqueue( exec );
197✔
336
}
197✔
337

338
unsigned int ScriptScheduler::get_new_pid( UOExecutor* exec )
280✔
339
{
340
  for ( ;; )
341
  {
342
    unsigned int newpid = next_pid;
280✔
343

344
    // increase next_pid and wrap so it's always within the positive values that fit an int
345
    if ( next_pid == INT_MAX )
280✔
346
      next_pid = PID_MIN;
×
347
    else
348
      ++next_pid;
280✔
349

350
    // NOTE: The code below is pessimistic, is there a way to avoid checking the pidlist every time?
351
    // (Nando, 06-Nov-2016)
352

353
    if ( pidlist.find( newpid ) == pidlist.end() )
280✔
354
    {
355
      pidlist[newpid] = exec;
280✔
356
      return newpid;
280✔
357
    }
358
  }
×
359
}
360

361
bool ScriptScheduler::find_exec( unsigned int pid, UOExecutor** exec )
131✔
362
{
363
  auto itr = pidlist.find( pid );
131✔
364
  if ( itr != pidlist.end() )
131✔
365
  {
366
    *exec = ( *itr ).second;
129✔
367
    return true;
129✔
368
  }
369
  else
370
  {
371
    *exec = nullptr;
2✔
372
    return false;
2✔
373
  }
374
}
375

376
bool ScriptScheduler::logScriptVariables( const std::string& name ) const
1✔
377
{
378
  std::string log = fmt::format( "{} {}\n", GET_LOG_FILESTAMP, name );
2✔
379
  std::vector<Bscript::Executor*> scripts;
1✔
380
  for ( const auto& exec : runlist )
2✔
381
  {
382
    if ( exec != nullptr && stricmp( exec->scriptname().c_str(), name.c_str() ) == 0 )
1✔
383
      scripts.push_back( exec );
×
384
  }
385
  for ( const auto& exec : ranlist )
1✔
386
  {
387
    if ( exec != nullptr && stricmp( exec->scriptname().c_str(), name.c_str() ) == 0 )
×
388
      scripts.push_back( exec );
×
389
  }
390
  for ( const auto& exec : holdlist )
3✔
391
  {
392
    if ( exec.second != nullptr && stricmp( exec.second->scriptname().c_str(), name.c_str() ) == 0 )
2✔
393
      scripts.push_back( exec.second );
×
394
  }
395
  for ( const auto& exec : notimeoutholdlist )
2✔
396
  {
397
    if ( exec != nullptr && stricmp( exec->scriptname().c_str(), name.c_str() ) == 0 )
1✔
398
      scripts.push_back( exec );
1✔
399
  }
400
  for ( const auto& exec : scripts )
2✔
401
  {
402
    fmt::format_to( std::back_inserter( log ), "Size: {}", exec->sizeEstimate() );
1✔
403
    auto prog = const_cast<Bscript::EScriptProgram*>( exec->prog() );
1✔
404
    if ( prog->read_dbg_file() != 0 )
1✔
405
    {
406
      log += " failed to load debug info\n";
×
407
      break;
×
408
    }
409
    size_t i = 0;
1✔
410
    log += "\nGlobals\n";
1✔
411
    for ( const auto& global : ( *exec->Globals2 ) )
1✔
412
    {
413
      fmt::format_to(
×
414
          std::back_inserter( log ), "  {} ({}) {}\n",
×
415
          prog->globalvarnames.size() > i ? prog->globalvarnames[i] : std::to_string( i ),
×
416
          global->impref().typeOf(), global->impref().sizeEstimate() );
×
417
    }
418
    log += "Locals\n";
1✔
419
    auto log_stack = [&]( unsigned PC, Bscript::BObjectRefVec* locals )
1✔
420
    {
421
      fmt::format_to( std::back_inserter( log ), "  {}: {}\n",
1✔
422
                      prog->dbg_filenames[prog->dbg_filenum[PC]], prog->dbg_linenum[PC] );
1✔
423

424

425
      unsigned block = prog->dbg_ins_blocks[PC];
1✔
426
      size_t left = locals->size();
1✔
427
      while ( left )
25✔
428
      {
429
        while ( left <= prog->blocks[block].parentvariables )
30✔
430
        {
431
          block = prog->blocks[block].parentblockidx;
6✔
432
        }
433
        const Bscript::EPDbgBlock& progblock = prog->blocks[block];
24✔
434
        size_t varidx = left - 1 - progblock.parentvariables;
24✔
435
        left--;
24✔
436
        Bscript::BObjectImp* ptr = ( *locals )[varidx]->impptr();
24✔
437
        fmt::format_to( std::back_inserter( log ), "  {} ({}) {}\n",
24✔
438
                        progblock.localvarnames[varidx], ptr->typeOf(), ptr->sizeEstimate() );
48✔
439
      }
440
    };
1✔
441
    log_stack( exec->PC, exec->Locals2 );
1✔
442
    for ( int stack_i = static_cast<int>( exec->ControlStack.size() ) - 1; stack_i >= 0; --stack_i )
1✔
443
    {
444
      fmt::format_to( std::back_inserter( log ), "Stack {}\n", stack_i );
×
445
      log_stack( exec->ControlStack[stack_i].PC, exec->upperLocals2[stack_i] );
×
446
    }
447
  }
448
  auto logf = OPEN_FLEXLOG( "log/scriptmemory.log", false );
1✔
449
  FLEXLOGLN( logf, log );
1✔
450
  CLOSE_FLEXLOG( logf );
1✔
451
  return true;
1✔
452
}
1✔
453

454
void ScriptScheduler::revive_debugged( UOExecutor* exec )
9✔
455
{
456
  debuggerholdlist.erase( exec );
9✔
457
  enqueue( exec );
9✔
458
}
9✔
459

460
void ScriptScheduler::revive_timeout( UOExecutor* exec, TimeoutHandle hold_itr )
27,539,505✔
461
{
462
  holdlist.erase( hold_itr );
27,539,505✔
463
  enqueue( exec );
27,539,505✔
464
}
27,539,505✔
465

466
void ScriptScheduler::revive_notimeout( UOExecutor* exec )
782✔
467
{
468
  notimeoutholdlist.erase( exec );
782✔
469
  enqueue( exec );
782✔
470
}
782✔
471

472
void ScriptScheduler::enqueue( UOExecutor* exec )
27,540,493✔
473
{
474
  passert_always( exec->in_hold_list() == NO_LIST );
27,540,493✔
475
  runlist.push_back( exec );
27,540,493✔
476
}
27,540,493✔
477

478
void ScriptScheduler::free_pid( unsigned int pid )
280✔
479
{
480
  pidlist.erase( pid );
280✔
481
}
280✔
482
}  // namespace Core
483
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc