278 const std::size_t nidx =
lsn.nidx;
279 if (nidx == 0)
throw InputError(
"SolverLDES (native LN engine): empty layered model");
282 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
283 if (t <
lsn.iscache.size() &&
lsn.iscache[t])
285 "' is not ported yet");
288 if (t <
lsn.hassetup.size() &&
lsn.hassetup[t]) {
289 const double m = (t <
lsn.mult.size()) ?
lsn.mult[t] : 1.0;
292 "' declares a setup time on an infinite-server task, which "
293 "holds no thread to power down; give it a finite "
300 for (std::size_t k = 1; k <
lsn.lincon_A.size(); ++k) {
301 if (
lsn.lincon_A[k].rows() > 0)
302 throw UnsupportedError(
"SolverLDES (native LN engine): the admission constraint on '" +
303 lsn.names[k] +
"' is not ported yet; solve this model with the "
313 for (std::size_t k = 0; k <
lsn.pools.size(); ++k) {
314 if (
lsn.pools[k].npools() > 0)
316 "' declares heterogeneous server pools, which are not ported "
317 "yet; solve this model with the Java LDES engine");
319 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts; ++a) {
323 "' has a non-sequential precedence, which is not ported yet");
327 "' has a non-sequential post-precedence, which is not ported "
331 const std::uint64_t max_events =
333 const std::uint64_t base =
334 (o.
seed >= 0) ?
static_cast<std::uint64_t
>(o.
seed) : std::random_device{}();
345 const long long ln_seed =
static_cast<long long>(base);
346 std::vector<Rng> g_host, g_think;
347 g_host.reserve(nidx + 1);
348 g_think.reserve(nidx + 1);
349 for (std::size_t k = 0; k <= nidx; ++k) {
350 g_host.push_back(Rng(ln_seed,
static_cast<long long>(k) * 10 + 1000));
351 g_think.push_back(Rng(ln_seed,
static_cast<long long>(k) * 10));
353 Rng g_call(ln_seed, 900000);
356 std::vector<Sampler> hostdem(nidx + 1), think(nidx + 1);
357 std::vector<bool> has_hostdem(nidx + 1,
false), has_think(nidx + 1,
false);
358 for (std::size_t k = 1; k <= nidx; ++k) {
359 if (k <
lsn.hostdem.size() && !
lsn.hostdem[k].disabled &&
361 hostdem[k] = Sampler(
lsn.hostdem[k],
"the host demand of '" +
lsn.names[k] +
"'");
362 has_hostdem[k] =
true;
364 if (k <
lsn.think.size() && !
lsn.think[k].disabled &&
366 think[k] = Sampler(
lsn.think[k],
"the think time of '" +
lsn.names[k] +
"'");
372 std::vector<std::size_t> host_servers(nidx + 1, 1), task_threads(nidx + 1, 1);
373 std::vector<SchedStrategy> host_sched(nidx + 1, SchedStrategy::FCFS);
374 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h) {
375 const double m = (h <
lsn.mult.size()) ?
lsn.mult[h] : 1.0;
376 host_servers[h] = std::isfinite(m) ?
static_cast<std::size_t
>(m + 0.5)
377 : std::numeric_limits<std::size_t>::max();
378 if (h <
lsn.sched.size()) host_sched[h] =
lsn.sched[h];
380 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
381 const double m = (t <
lsn.mult.size()) ?
lsn.mult[t] : 1.0;
382 task_threads[t] = std::isfinite(m) ?
static_cast<std::size_t
>(m + 0.5)
383 : std::numeric_limits<std::size_t>::max();
396 std::vector<std::size_t> repl(nidx + 1, 1);
397 for (std::size_t k = 1; k <
lsn.repl.size() && k <= nidx; ++k) {
398 const double r =
lsn.repl[k];
399 repl[k] = (r > 1.0) ?
static_cast<std::size_t
>(r + 0.5) : 1;
401 std::vector<std::size_t> host_slot0(nidx + 1, 0);
402 std::size_t nhost_slots = 0;
403 for (std::size_t h =
lsn.hshift + 1; h <=
lsn.hshift +
lsn.nhosts; ++h) {
404 host_slot0[h] = nhost_slots;
405 nhost_slots += repl[h];
407 if (nhost_slots == 0) nhost_slots = 1;
410 auto host_slot = [&](std::size_t host, std::size_t trep) {
411 return host_slot0[host] + (trep % repl[host]);
416 std::vector<std::size_t> task_slot0(nidx + 1, 0);
417 std::size_t ntask_slots = 0;
418 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
419 task_slot0[t] = ntask_slots;
420 ntask_slots += repl[t];
422 if (ntask_slots == 0) ntask_slots = 1;
423 auto task_slot = [&](std::size_t task, std::size_t trep) {
424 return task_slot0[task] + (trep % repl[task]);
436 auto callee_replica = [&](std::size_t caller_task, std::size_t crep,
437 std::size_t callee_task) -> std::size_t {
438 const std::size_t rb = repl[callee_task];
439 if (rb <= 1)
return 0;
440 std::size_t f =
static_cast<std::size_t
>(
lsn.fanout_at(caller_task, callee_task) + 0.5);
442 const std::size_t ra = repl[caller_task];
443 f = (rb > ra) ? std::max<std::size_t>(1, rb / ra) : 1;
445 f = std::min(std::max<std::size_t>(1, f), rb);
446 const std::size_t k =
447 (f > 1) ?
static_cast<std::size_t
>(uniform01(g_call) *
static_cast<double>(f)) : 0;
448 return ((crep * f) + std::min(k, f - 1)) % rb;
453 std::priority_queue<LnEvent, std::vector<LnEvent>, LnEventLater> evq;
454 std::uint64_t seq = 0;
455 std::map<std::size_t, LnJob> jobs;
456 std::size_t next_job = 0;
459 std::vector<std::size_t> host_busy(nhost_slots, 0);
460 std::vector<std::deque<std::size_t>> host_queue(nhost_slots);
461 std::vector<std::size_t> running_on(nhost_slots, 0);
465 std::vector<std::vector<bool>> thr_held(ntask_slots);
466 std::vector<std::vector<ThreadState>> thr_state(ntask_slots);
467 std::vector<std::vector<double>> thr_setup_t0(ntask_slots);
468 std::vector<std::vector<std::uint64_t>> thr_gen(ntask_slots);
469 std::vector<std::deque<std::size_t>> thr_queue(ntask_slots);
470 std::vector<std::size_t> thr_owner(ntask_slots, 0);
471 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
472 const std::size_t n = (task_threads[t] == std::numeric_limits<std::size_t>::max())
473 ? 0 : task_threads[t];
474 for (std::size_t m = 0; m < repl[t]; ++m) {
475 const std::size_t ts = task_slot(t, m);
477 thr_held[ts].assign(n,
false);
478 thr_state[ts].assign(n, ThreadState::ACTIVE);
479 thr_setup_t0[ts].assign(n, 0.0);
480 thr_gen[ts].assign(n, 0);
487 std::vector<Sampler> setupd(nidx + 1), delayoffd(nidx + 1);
488 std::vector<bool> has_setup(nidx + 1,
false);
489 std::vector<Rng> g_setup, g_doff;
490 g_setup.reserve(nidx + 1);
491 g_doff.reserve(nidx + 1);
492 for (std::size_t k = 0; k <= nidx; ++k) {
493 g_setup.push_back(Rng(ln_seed,
static_cast<long long>(k) * 10 + 2000));
494 g_doff.push_back(Rng(ln_seed,
static_cast<long long>(k) * 10 + 3000));
496 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
497 if (t >=
lsn.hassetup.size() || !
lsn.hassetup[t])
continue;
498 const bool su = t <
lsn.setuptime.size() && !
lsn.setuptime[t].disabled &&
500 const bool df = t <
lsn.delayofftime.size() && !
lsn.delayofftime[t].disabled &&
502 if (!su || !df)
continue;
503 setupd[t] = Sampler(
lsn.setuptime[t],
"the setup time of '" +
lsn.names[t] +
"'");
504 delayoffd[t] = Sampler(
lsn.delayofftime[t],
"the delay-off time of '" +
lsn.names[t] +
"'");
511 std::deque<std::size_t> runnable;
514 std::vector<double> tot_q(nidx + 1, 0.0), tot_u(nidx + 1, 0.0);
515 std::vector<double> cur_q(nidx + 1, 0.0), cur_u(nidx + 1, 0.0);
516 std::vector<double> last_upd(nidx + 1, 0.0);
517 std::vector<double> completions(nidx + 1, 0.0), resp_sum(nidx + 1, 0.0),
518 resp_cnt(nidx + 1, 0.0);
522 std::vector<std::vector<double> > entry_resp_samples(
lsn.nentries);
530 std::vector<double> act_host_resid(nidx + 1, 0.0);
535 std::vector<Rng> g_callmult;
536 g_callmult.reserve(
lsn.ncalls + 1);
537 for (std::size_t c = 0; c <=
lsn.ncalls; ++c)
538 g_callmult.push_back(Rng(ln_seed,
static_cast<long long>(c) * 10 + 4000));
548 auto sample_call_count = [&](std::size_t cidx) -> std::size_t {
549 const double m = (cidx <
lsn.callproc_mean.size())
552 if (!(m > 0.0))
return 0;
553 std::size_t n =
static_cast<std::size_t
>(std::floor(m));
554 const double frac = m -
static_cast<double>(n);
555 if (frac > 0.0 && cidx < g_callmult.size() && uniform01(g_callmult[cidx]) < frac) ++n;
559 auto touch = [&](std::size_t k) {
560 const double dt = now - last_upd[k];
562 tot_q[k] += cur_q[k] * dt;
563 tot_u[k] += cur_u[k] * dt;
567 auto push_ev = [&](LnEvent e) {
572 std::uint64_t done = 0;
575 std::function<void(std::size_t)> advance;
578 while (!runnable.empty()) {
579 const std::size_t
id = runnable.front();
580 runnable.pop_front();
586 auto start_host = [&](std::size_t jid, std::size_t act, std::size_t trep) {
587 const std::size_t task =
lsn.parent[act];
588 const std::size_t host =
lsn.host_of(act);
589 const std::size_t hs = host_slot(host, trep);
594 jobs[jid].host_t0 = now;
595 if (host_busy[hs] < host_servers[host]) {
598 running_on[hs] = jid;
600 e.t = now + hostdem[act].next(g_host[act]);
605 host_queue[hs].push_back(jid);
611 auto start_setup = [&](std::size_t ts, std::size_t th) {
612 const std::size_t task = thr_owner[ts];
613 thr_state[ts][th] = ThreadState::SETUP;
614 thr_setup_t0[ts][th] = now;
616 e.t = now + setupd[task].next(g_setup[task]);
620 e.gen = ++thr_gen[ts][th];
625 auto start_delayoff = [&](std::size_t ts, std::size_t th) {
626 const std::size_t task = thr_owner[ts];
627 thr_state[ts][th] = ThreadState::DELAYOFF;
629 e.t = now + delayoffd[task].next(g_doff[task]);
633 e.gen = ++thr_gen[ts][th];
646 auto acquire_thread = [&](std::size_t jid, std::size_t entry) ->
bool {
647 LnJob& j = jobs[jid];
648 const std::size_t task =
lsn.parent[entry];
649 const std::size_t trep = j.repl_of.back();
650 const std::size_t ts = task_slot(task, trep);
652 if (task_threads[task] == std::numeric_limits<std::size_t>::max()) {
654 j.thr_slot.back() = ts;
655 j.thr_id.back() = NOTHR;
658 std::vector<bool>& held = thr_held[ts];
659 std::vector<ThreadState>& st = thr_state[ts];
663 for (std::size_t th = 0; th < held.size(); ++th) {
664 if (!held[th] && st[th] == ThreadState::ACTIVE) {
667 j.thr_slot.back() = ts;
668 j.thr_id.back() = th;
672 for (std::size_t th = 0; th < held.size(); ++th) {
673 if (!held[th] && st[th] == ThreadState::DELAYOFF) {
675 st[th] = ThreadState::ACTIVE;
678 j.thr_slot.back() = ts;
679 j.thr_id.back() = th;
683 thr_queue[ts].push_back(jid);
684 for (std::size_t th = 0; th < held.size(); ++th) {
685 if (!held[th] && st[th] == ThreadState::OFF) {
694 auto release_thread = [&](std::size_t jid) {
695 LnJob& j = jobs[jid];
696 const std::size_t ts = j.thr_slot.back();
697 const std::size_t th = j.thr_id.back();
698 const std::size_t task = thr_owner[ts];
701 j.thr_slot.back() = NOTHR;
702 j.thr_id.back() = NOTHR;
703 if (th == NOTHR)
return;
704 thr_held[ts][th] =
false;
705 if (!thr_queue[ts].empty()) {
706 const std::size_t nxt = thr_queue[ts].front();
707 thr_queue[ts].pop_front();
708 LnJob& nj = jobs[nxt];
709 thr_held[ts][th] =
true;
711 nj.thr_slot.back() = ts;
712 nj.thr_id.back() = th;
713 runnable.push_back(nxt);
714 }
else if (has_setup[task]) {
715 start_delayoff(ts, th);
734 auto finish_act = [&](LnJob& j, std::size_t act) {
735 const double t0 = j.act_t0.empty() ? -1.0 : j.act_t0.back();
736 completions[act] += 1.0;
738 resp_sum[act] += now - t0;
739 resp_cnt[act] += 1.0;
743 if (!j.act_t0.empty()) j.act_t0.back() = -1.0;
746 advance = [&](std::size_t jid) {
747 LnJob& j = jobs[jid];
749 if (j.stack.empty()) {
760 const std::size_t rt = j.ref_task;
761 resp_sum[rt] += now - j.t_start;
765 e.t = now + (has_think[rt] ? think[rt].next(g_think[rt]) : 0.0);
771 const std::size_t entry = j.stack.back();
774 if (j.thr_slot.back() == NOTHR && j.thr_id.back() == NOTHR) {
775 if (!acquire_thread(jid, entry))
return;
777 const std::vector<std::size_t>& acts =
778 (entry <
lsn.actsof.size()) ?
lsn.actsof[entry] : std::vector<std::size_t>();
779 if (j.act_pos.back() >= acts.size()) {
788 completions[entry] += 1.0;
789 const double e0 = j.entry_t0.back();
790 resp_sum[entry] += now - e0;
791 resp_cnt[entry] += 1.0;
795 if (entry >
lsn.eshift && entry <=
lsn.eshift +
lsn.nentries) {
796 entry_resp_samples[entry -
lsn.eshift - 1].push_back(now - e0);
800 const std::size_t task =
lsn.parent[entry];
801 completions[task] += 1.0;
806 j.act_pos.pop_back();
807 j.call_pos.pop_back();
808 j.calls_left.pop_back();
809 j.entry_t0.pop_back();
811 j.repl_of.pop_back();
812 j.thr_slot.pop_back();
816 const std::size_t act = acts[j.act_pos.back()];
820 if (j.act_t0.back() < 0.0) {
821 j.act_t0.back() = now;
825 const std::vector<std::size_t>& calls =
826 (act <
lsn.callsof.size()) ?
lsn.callsof[act] : std::vector<std::size_t>();
827 if (j.call_pos.back() < calls.size()) {
833 const std::size_t cidx = calls[j.call_pos.back()];
834 if (j.calls_left.back() == NOTDRAWN)
835 j.calls_left.back() = sample_call_count(cidx);
836 if (j.calls_left.back() == 0) {
840 j.calls_left.back() = NOTDRAWN;
843 --j.calls_left.back();
844 if (j.calls_left.back() == 0) {
846 j.calls_left.back() = NOTDRAWN;
848 const std::size_t callee =
lsn.callpair_dst[cidx];
853 j.stack.push_back(callee);
854 j.act_pos.push_back(0);
855 j.call_pos.push_back(0);
856 j.calls_left.push_back(NOTDRAWN);
857 j.entry_t0.push_back(now);
858 j.act_t0.push_back(-1.0);
859 j.repl_of.push_back(callee_replica(
lsn.parent[entry], j.repl_of.back(),
860 lsn.parent[callee]));
861 j.thr_slot.push_back(NOTHR);
862 j.thr_id.push_back(NOTHR);
863 touch(
lsn.parent[callee]);
864 cur_q[
lsn.parent[callee]] += 1.0;
866 cur_q[callee] += 1.0;
871 completions[callee] += 1.0;
876 j.call_pos.back() = 0;
877 j.calls_left.back() = NOTDRAWN;
878 if (has_hostdem[act]) {
879 start_host(jid, act, j.repl_of.back());
891 bool any_ref =
false;
892 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks; ++t) {
893 if (t >=
lsn.isref.size() || !
lsn.isref[t])
continue;
895 const std::size_t n = (task_threads[t] == std::numeric_limits<std::size_t>::max())
898 const std::vector<std::size_t>& es =
899 (t <
lsn.entriesof.size()) ?
lsn.entriesof[t] : std::vector<std::size_t>();
901 throw InputError(
"SolverLDES (native LN engine): reference task '" +
lsn.names[t] +
902 "' declares no entry to invoke");
905 for (std::size_t m = 0; m < repl[t]; ++m) {
906 for (std::size_t k = 0; k < n; ++k) {
911 j.stack.push_back(es[0]);
912 j.act_pos.push_back(0);
913 j.call_pos.push_back(0);
914 j.calls_left.push_back(NOTDRAWN);
915 j.entry_t0.push_back(now);
916 j.act_t0.push_back(-1.0);
917 j.repl_of.push_back(m);
918 j.thr_slot.push_back(NOTHR);
919 j.thr_id.push_back(NOTHR);
931 throw InputError(
"SolverLDES (native LN engine): the model has no reference task, so "
932 "nothing drives it");
935 while (!evq.empty() && done < max_events) {
936 const LnEvent ev = evq.top();
942 LnJob& j = jobs[ev.who];
943 const std::vector<std::size_t>& es =
lsn.entriesof[j.ref_task];
945 const std::size_t rep = j.repl_of.empty() ? 0 : j.repl_of.front();
946 j.stack.assign(1, es[0]);
947 j.act_pos.assign(1, 0);
948 j.call_pos.assign(1, 0);
949 j.calls_left.assign(1, NOTDRAWN);
950 j.entry_t0.assign(1, now);
951 j.act_t0.assign(1, -1.0);
954 j.repl_of.assign(1, rep);
955 j.thr_slot.assign(1, NOTHR);
956 j.thr_id.assign(1, NOTHR);
958 cur_q[j.ref_task] += 1.0;
966 if (ev.kind == 2 || ev.kind == 3) {
967 const std::size_t ts = ev.who, th = ev.aux;
968 if (ev.gen != thr_gen[ts][th])
continue;
970 if (thr_state[ts][th] == ThreadState::DELAYOFF)
971 thr_state[ts][th] = ThreadState::OFF;
976 thr_state[ts][th] = ThreadState::ACTIVE;
977 if (!thr_held[ts][th] && !thr_queue[ts].empty()) {
978 const std::size_t nxt = thr_queue[ts].front();
979 thr_queue[ts].pop_front();
980 LnJob& nj = jobs[nxt];
981 thr_held[ts][th] =
true;
982 touch(thr_owner[ts]);
983 cur_u[thr_owner[ts]] += 1.0;
984 nj.thr_slot.back() = ts;
985 nj.thr_id.back() = th;
993 LnJob& j = jobs[ev.who];
994 const std::size_t entry = j.stack.empty() ? 0 : j.stack.back();
995 const std::vector<std::size_t>& acts =
996 (entry != 0 && entry <
lsn.actsof.size()) ?
lsn.actsof[entry]
997 : std::vector<std::size_t>();
1000 const std::size_t ai = j.act_pos.empty() ? 0 : j.act_pos.back();
1001 const std::size_t act = (ai > 0 && ai - 1 < acts.size()) ? acts[ai - 1] : 0;
1002 if (act == 0)
continue;
1003 const std::size_t host =
lsn.host_of(act);
1006 const std::size_t hs = host_slot(host, j.repl_of.empty() ? 0 : j.repl_of.back());
1011 completions[host] += 1.0;
1012 act_host_resid[act] += now - j.host_t0;
1017 if (!host_queue[hs].empty()) {
1018 const std::size_t nxt = host_queue[hs].front();
1019 host_queue[hs].pop_front();
1020 LnJob& nj = jobs[nxt];
1021 const std::size_t ne = nj.stack.back();
1022 const std::size_t nai = nj.act_pos.back();
1023 const std::size_t nact =
lsn.actsof[ne][nai - 1];
1027 e.t = now + hostdem[nact].next(g_host[nact]);
1037 for (std::size_t k = 1; k <= nidx; ++k) touch(k);
1046 res.WLN =
Matrix<double>(nidx + 1, 1, std::numeric_limits<double>::quiet_NaN());
1047 for (std::size_t k = 1; k <= nidx; ++k) {
1049 res.QLN(k, 0) = tot_q[k] / now;
1057 const bool is_task = (k >
lsn.tshift && k <=
lsn.tshift +
lsn.ntasks);
1058 const std::size_t cap_k = is_task ? task_threads[k] : host_servers[k];
1059 const double c = (cap_k == std::numeric_limits<std::size_t>::max())
1061 :
static_cast<double>(cap_k * repl[k]);
1062 res.ULN(k, 0) = tot_u[k] / (now * c);
1063 res.TLN(k, 0) = completions[k] / now;
1065 if (resp_cnt[k] > 0.0) res.RLN(k, 0) = resp_sum[k] / resp_cnt[k];
1067 res.entry_resp_samples.swap(entry_resp_samples);
1083 std::vector<double> task_resid(nidx + 1, 0.0);
1084 std::vector<bool> task_has_act(nidx + 1,
false);
1085 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <= nidx; ++a) {
1086 const std::size_t task =
lsn.parent[a];
1087 const double x_task = (task >= 1 && task <= nidx) ? res.TLN(task, 0) : 0.0;
1088 const double resid = (x_task > 0.0) ? act_host_resid[a] / (now * x_task) : 0.0;
1089 res.WLN(a, 0) = resid;
1090 if (task >= 1 && task <= nidx) {
1091 task_resid[task] += resid;
1092 task_has_act[task] =
true;
1095 for (std::size_t t =
lsn.tshift + 1; t <=
lsn.tshift +
lsn.ntasks && t <= nidx; ++t)
1096 if (task_has_act[t]) res.WLN(t, 0) = task_resid[t];
1113 std::vector<double> proc_util(nidx + 1, 0.0), task_proc_util(nidx + 1, 0.0);
1114 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <= nidx; ++a) {
1115 const double d = (a <
lsn.hostdem.size() && !
lsn.hostdem[a].disabled)
1118 proc_util[a] = res.TLN(a, 0) * d;
1119 const std::size_t task =
lsn.parent[a];
1120 if (task >= 1 && task <= nidx) task_proc_util[task] += proc_util[a];
1122 for (std::size_t e =
lsn.eshift + 1; e <=
lsn.eshift +
lsn.nentries && e <= nidx; ++e) {
1124 const std::vector<std::size_t>& acts =
1125 (e <
lsn.actsof.size()) ?
lsn.actsof[e] : std::vector<std::size_t>();
1126 for (std::size_t i = 0; i < acts.size(); ++i)
1127 if (acts[i] <= nidx) u += proc_util[acts[i]];
1130 for (std::size_t a =
lsn.ashift + 1; a <=
lsn.ashift +
lsn.nacts && a <= nidx; ++a) {
1131 const std::size_t task =
lsn.parent[a];
1132 const double tu = (task >= 1 && task <= nidx) ? task_proc_util[task] : 0.0;
1133 if (tu > 0.0 && tu < 1.0)
1134 res.ULN(a, 0) = std::min(1.0, proc_util[a] / tu);
1136 res.ULN(a, 0) = (proc_util[a] > 0.0) ? 1.0 : 0.0;
1140 res.simulated_time = now;
1141 res.completions =
static_cast<long long>(done);