salabim 24.0.17__py3-none-any.whl → 25.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- salabim/LICENSE.txt +1 -1
- salabim/salabim.py +373 -140
- {salabim-24.0.17.dist-info → salabim-25.0.0.dist-info}/METADATA +2 -2
- salabim-25.0.0.dist-info/RECORD +10 -0
- {salabim-24.0.17.dist-info → salabim-25.0.0.dist-info}/WHEEL +1 -1
- salabim-24.0.17.dist-info/RECORD +0 -10
- {salabim-24.0.17.dist-info → salabim-25.0.0.dist-info}/top_level.txt +0 -0
salabim/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (C) 2017-
|
3
|
+
Copyright (C) 2017-2025 Ruud van der Ham, ruud@salabim.org
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
6
|
this software and associated documentation files (the "Software"), to deal in
|
salabim/salabim.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
# _ _ _ ____
|
2
|
-
# ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ |
|
3
|
-
# / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) ||
|
4
|
-
# \__ \| (_| || || (_| || |_) || || | | | | | / __/ |
|
5
|
-
# |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____
|
1
|
+
# _ _ _ ____ ____ ___ ___
|
2
|
+
# ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | ___| / _ \ / _ \
|
3
|
+
# / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) ||___ \ | | | | | | | |
|
4
|
+
# \__ \| (_| || || (_| || |_) || || | | | | | / __/ ___) | _ | |_| | _ | |_| |
|
5
|
+
# |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____||____/ (_) \___/ (_) \___/
|
6
6
|
# discrete event simulation
|
7
7
|
#
|
8
8
|
# see www.salabim.org for more information, the documentation and license information
|
9
9
|
|
10
|
-
__version__ = "
|
11
|
-
|
10
|
+
__version__ = "25.0.0"
|
12
11
|
import heapq
|
13
12
|
import random
|
14
13
|
import time
|
@@ -31,7 +30,6 @@ import subprocess
|
|
31
30
|
import tempfile
|
32
31
|
import struct
|
33
32
|
import binascii
|
34
|
-
import operator
|
35
33
|
import copy
|
36
34
|
import numbers
|
37
35
|
import platform
|
@@ -43,8 +41,12 @@ import urllib.request
|
|
43
41
|
import urllib.error
|
44
42
|
import base64
|
45
43
|
import zipfile
|
46
|
-
|
47
44
|
from pathlib import Path
|
45
|
+
try:
|
46
|
+
import peek
|
47
|
+
except ModuleNotFoundError:
|
48
|
+
...
|
49
|
+
|
48
50
|
|
49
51
|
from typing import Any, Union, Iterable, Tuple, List, Callable, TextIO, Dict, Set, Type, Hashable, Optional
|
50
52
|
|
@@ -62,6 +64,46 @@ Chromebook = "penguin" in platform.uname()
|
|
62
64
|
PythonInExcel = not ("__file__" in globals())
|
63
65
|
AnacondaCode = sys.platform == "emscripten"
|
64
66
|
|
67
|
+
_color_name_to_ANSI = dict(
|
68
|
+
dark_black="\033[0;30m",
|
69
|
+
dark_red="\033[0;31m",
|
70
|
+
dark_green="\033[0;32m",
|
71
|
+
dark_yellow="\033[0;33m",
|
72
|
+
dark_blue="\033[0;34m",
|
73
|
+
dark_magenta="\033[0;35m",
|
74
|
+
dark_cyan="\033[0;36m",
|
75
|
+
dark_white="\033[0;37m",
|
76
|
+
black="\033[1;30m",
|
77
|
+
red="\033[1;31m",
|
78
|
+
green="\033[1;32m",
|
79
|
+
yellow="\033[1;33m",
|
80
|
+
blue="\033[1;34m",
|
81
|
+
magenta="\033[1;35m",
|
82
|
+
cyan="\033[1;36m",
|
83
|
+
white="\033[1;37m",
|
84
|
+
reset="\033[0m",
|
85
|
+
)
|
86
|
+
_ANSI_to_rgb = {
|
87
|
+
"\033[1;30m": (51, 51, 51),
|
88
|
+
"\033[1;31m": (255, 0, 0),
|
89
|
+
"\033[1;32m": (0, 255, 0),
|
90
|
+
"\033[1;33m": (255, 255, 0),
|
91
|
+
"\033[1;34m": (0, 178, 255),
|
92
|
+
"\033[1;35m": (255, 0, 255),
|
93
|
+
"\033[1;36m": (0, 255, 255),
|
94
|
+
"\033[1;37m": (255, 255, 255),
|
95
|
+
"\033[0;30m": (76, 76, 76),
|
96
|
+
"\033[0;31m": (178, 0, 0),
|
97
|
+
"\033[0;32m": (0, 178, 0),
|
98
|
+
"\033[0;33m": (178, 178, 0),
|
99
|
+
"\033[0;34m": (0, 89, 255),
|
100
|
+
"\033[0;35m": (178, 0, 178),
|
101
|
+
"\033[0;36m": (0, 178, 178),
|
102
|
+
"\033[0;37m": (178, 178, 178),
|
103
|
+
"\033[0m": (),
|
104
|
+
}
|
105
|
+
|
106
|
+
ANSI=types.SimpleNamespace(_color_name_to_ANSI.items())
|
65
107
|
|
66
108
|
def a_log(*args):
|
67
109
|
if not hasattr(a_log, "a_logfile_name"):
|
@@ -4867,7 +4909,7 @@ class Queue:
|
|
4867
4909
|
"""
|
4868
4910
|
return getattr(self, "_sequence_number", 1)
|
4869
4911
|
|
4870
|
-
def add(self, component: "Component") -> "Queue":
|
4912
|
+
def add(self, component: "Component", priority: float = None) -> "Queue":
|
4871
4913
|
"""
|
4872
4914
|
adds a component to the tail of a queue
|
4873
4915
|
|
@@ -4878,18 +4920,23 @@ class Queue:
|
|
4878
4920
|
|
4879
4921
|
may not be member of the queue yet
|
4880
4922
|
|
4923
|
+
priority : float
|
4924
|
+
if None (default), add to the tail of the queue
|
4925
|
+
|
4926
|
+
otherwise, put after the last component with the same priority
|
4927
|
+
|
4881
4928
|
Note
|
4882
4929
|
----
|
4883
|
-
the priority will be set to
|
4930
|
+
if prioority is None, the priority will be set to
|
4884
4931
|
the priority of the tail of the queue, if any
|
4885
4932
|
or 0 if queue is empty
|
4886
4933
|
|
4887
4934
|
This method is equivalent to append()
|
4888
4935
|
"""
|
4889
|
-
component.enter(self)
|
4936
|
+
component.enter(self, priority)
|
4890
4937
|
return self
|
4891
4938
|
|
4892
|
-
def append(self, component: "Component") -> "Queue":
|
4939
|
+
def append(self, component: "Component", priority: float = None) -> "Queue":
|
4893
4940
|
"""
|
4894
4941
|
appends a component to the tail of a queue
|
4895
4942
|
|
@@ -4900,9 +4947,14 @@ class Queue:
|
|
4900
4947
|
|
4901
4948
|
may not be member of the queue yet
|
4902
4949
|
|
4950
|
+
priority : float
|
4951
|
+
if None (default), add to the tail of the queue
|
4952
|
+
|
4953
|
+
otherwise, put after the last component with the same priority
|
4954
|
+
|
4903
4955
|
Note
|
4904
4956
|
----
|
4905
|
-
the priority will be set to
|
4957
|
+
if priority is None, the priority will be set to
|
4906
4958
|
the priority of the tail of the queue, if any
|
4907
4959
|
or 0 if queue is empty
|
4908
4960
|
|
@@ -5014,7 +5066,7 @@ class Queue:
|
|
5014
5066
|
component.enter_behind(self, poscomponent)
|
5015
5067
|
return self
|
5016
5068
|
|
5017
|
-
def add_sorted(self, component: "Component", priority:
|
5069
|
+
def add_sorted(self, component: "Component", priority: float) -> "Queue":
|
5018
5070
|
"""
|
5019
5071
|
adds a component to a queue, according to the priority
|
5020
5072
|
|
@@ -5025,7 +5077,7 @@ class Queue:
|
|
5025
5077
|
|
5026
5078
|
may not be member of the queue yet
|
5027
5079
|
|
5028
|
-
priority:
|
5080
|
+
priority: float
|
5029
5081
|
priority in the queue
|
5030
5082
|
|
5031
5083
|
Note
|
@@ -7317,6 +7369,29 @@ by adding at the end:
|
|
7317
7369
|
def __repr__(self):
|
7318
7370
|
return object_to_str(self) + " (" + self.name() + ")"
|
7319
7371
|
|
7372
|
+
def reset_monitors(self, monitor: bool = None, stats_only: bool = None) -> None:
|
7373
|
+
"""
|
7374
|
+
resets the monitor for the component's status and mode monitors
|
7375
|
+
|
7376
|
+
Parameters
|
7377
|
+
----------
|
7378
|
+
monitor : bool
|
7379
|
+
if True, monitoring will be on.
|
7380
|
+
|
7381
|
+
if False, monitoring is disabled
|
7382
|
+
|
7383
|
+
if omitted, no change of monitoring state
|
7384
|
+
|
7385
|
+
stats_only : bool
|
7386
|
+
if True, only statistics will be collected (using less memory, but also less functionality)
|
7387
|
+
|
7388
|
+
if False, full functionality
|
7389
|
+
|
7390
|
+
if omittted, no change of stats_only
|
7391
|
+
"""
|
7392
|
+
self.status.reset(monitor=monitor, stats_only=stats_only)
|
7393
|
+
self.mode.reset(monitor=monitor, stats_only=stats_only)
|
7394
|
+
|
7320
7395
|
def register(self, registry: List) -> "Component":
|
7321
7396
|
"""
|
7322
7397
|
registers the component in the registry
|
@@ -8050,6 +8125,7 @@ by adding:
|
|
8050
8125
|
self,
|
8051
8126
|
store: Union["Store", Iterable],
|
8052
8127
|
filter: Callable = lambda c: True,
|
8128
|
+
request_priority: Any = 0,
|
8053
8129
|
fail_priority: float = 0,
|
8054
8130
|
urgent: bool = True,
|
8055
8131
|
fail_at: float = None,
|
@@ -8073,6 +8149,9 @@ by adding:
|
|
8073
8149
|
|
8074
8150
|
default: lambda c: True (i.e. always return True)
|
8075
8151
|
|
8152
|
+
request_priority: float
|
8153
|
+
put component in to_store_requesters according to the given priority (default 0)
|
8154
|
+
|
8076
8155
|
fail_priority : float
|
8077
8156
|
priority of the fail event
|
8078
8157
|
|
@@ -8213,7 +8292,7 @@ by adding:
|
|
8213
8292
|
|
8214
8293
|
self._from_stores = from_stores
|
8215
8294
|
for store in from_stores:
|
8216
|
-
self.
|
8295
|
+
self.enter_sorted(store._from_store_requesters, priority=request_priority)
|
8217
8296
|
self.status._value = requesting
|
8218
8297
|
self._from_store_item = None
|
8219
8298
|
self._from_store_filter = filter
|
@@ -8226,6 +8305,7 @@ by adding:
|
|
8226
8305
|
self,
|
8227
8306
|
store: Union["Store", Iterable],
|
8228
8307
|
item: "Component",
|
8308
|
+
request_priority: float = None,
|
8229
8309
|
priority: float = 0,
|
8230
8310
|
fail_priority: float = 0,
|
8231
8311
|
urgent: bool = True,
|
@@ -8245,6 +8325,9 @@ by adding:
|
|
8245
8325
|
item: Component
|
8246
8326
|
component to put to store
|
8247
8327
|
|
8328
|
+
request_priority: float
|
8329
|
+
put component in to_store_requesters according to the given priority (default 0)
|
8330
|
+
|
8248
8331
|
fail_priority : float
|
8249
8332
|
priority of the fail event
|
8250
8333
|
|
@@ -8256,11 +8339,11 @@ by adding:
|
|
8256
8339
|
urgent : bool
|
8257
8340
|
urgency indicator
|
8258
8341
|
|
8259
|
-
if False (default), the
|
8342
|
+
if False (default), the fail event will be scheduled
|
8260
8343
|
behind all other components scheduled
|
8261
8344
|
for the same time and priority
|
8262
8345
|
|
8263
|
-
if True, the
|
8346
|
+
if True, the fail event will be scheduled
|
8264
8347
|
in front of all components scheduled
|
8265
8348
|
for the same time and priority
|
8266
8349
|
|
@@ -8358,7 +8441,7 @@ by adding:
|
|
8358
8441
|
return
|
8359
8442
|
|
8360
8443
|
for store in to_stores:
|
8361
|
-
self.
|
8444
|
+
self.enter_sorted(store._to_store_requesters, priority=request_priority)
|
8362
8445
|
self.status._value = requesting
|
8363
8446
|
self._to_store_item = item
|
8364
8447
|
self._to_store_priority = priority
|
@@ -8384,7 +8467,19 @@ by adding:
|
|
8384
8467
|
for store in self._from_stores:
|
8385
8468
|
store.rescan()
|
8386
8469
|
|
8387
|
-
def request(
|
8470
|
+
def request(
|
8471
|
+
self,
|
8472
|
+
*args,
|
8473
|
+
fail_at: float = None,
|
8474
|
+
fail_delay: float = None,
|
8475
|
+
mode: Any = None,
|
8476
|
+
urgent: bool = False,
|
8477
|
+
request_priority: float = 0,
|
8478
|
+
schedule_priority: float = 0,
|
8479
|
+
cap_now: bool = None,
|
8480
|
+
oneof: bool = False,
|
8481
|
+
called_from: str = "request",
|
8482
|
+
) -> None:
|
8388
8483
|
"""
|
8389
8484
|
request from a resource or resources
|
8390
8485
|
|
@@ -8394,9 +8489,12 @@ by adding:
|
|
8394
8489
|
- resource, where quantity=1, priority=tail of requesters queue
|
8395
8490
|
- tuples/list containing a resource, a quantity and optionally a priority.
|
8396
8491
|
if the priority is not specified, the request
|
8397
|
-
for the resource be added to the
|
8398
|
-
|
8492
|
+
for the resource will be added according to the request_priority parameter
|
8493
|
+
|
8494
|
+
request_priority: float
|
8495
|
+
(may be overridden by the priority parameter in the arg sequence)
|
8399
8496
|
|
8497
|
+
put component requesters according to the given priority (default 0)
|
8400
8498
|
|
8401
8499
|
priority : float
|
8402
8500
|
priority of the fail event
|
@@ -8499,17 +8597,7 @@ by adding:
|
|
8499
8597
|
--> requests 1 from r1, r2 or r3
|
8500
8598
|
|
8501
8599
|
"""
|
8502
|
-
fail_at = kwargs.pop("fail_at", None)
|
8503
|
-
fail_delay = kwargs.pop("fail_delay", None)
|
8504
|
-
mode = kwargs.pop("mode", None)
|
8505
|
-
urgent = kwargs.pop("urgent", False)
|
8506
|
-
schedule_priority = kwargs.pop("priority", 0)
|
8507
|
-
cap_now = kwargs.pop("cap_now", None)
|
8508
|
-
oneof = kwargs.pop("oneof", False)
|
8509
|
-
called_from = kwargs.pop("called_from", "request")
|
8510
8600
|
self.oneof_request = oneof
|
8511
|
-
if kwargs:
|
8512
|
-
raise TypeError(called_from + "() got an unexpected keyword argument '" + tuple(kwargs)[0] + "'")
|
8513
8601
|
|
8514
8602
|
if self.status.value != current:
|
8515
8603
|
self._checkisnotdata()
|
@@ -8542,7 +8630,7 @@ by adding:
|
|
8542
8630
|
return
|
8543
8631
|
for arg in args:
|
8544
8632
|
q = 1
|
8545
|
-
priority =
|
8633
|
+
priority = request_priority
|
8546
8634
|
if isinstance(arg, Resource):
|
8547
8635
|
r = arg
|
8548
8636
|
elif isinstance(arg, (tuple, list)):
|
@@ -8580,7 +8668,7 @@ by adding:
|
|
8580
8668
|
if self.oneof_request:
|
8581
8669
|
addstring += " (oneof)"
|
8582
8670
|
|
8583
|
-
self.
|
8671
|
+
self.enter(r._requesters, priority)
|
8584
8672
|
if self.env._trace:
|
8585
8673
|
self.env.print_trace("", "", self.name(), req_text + r.name() + addstring)
|
8586
8674
|
|
@@ -8829,7 +8917,144 @@ by adding:
|
|
8829
8917
|
for r in list(self._claims):
|
8830
8918
|
self._release(r)
|
8831
8919
|
|
8832
|
-
def
|
8920
|
+
def wait_for(self, cond, states, request_priority=0, priority=0, urgent=False, mode=None, fail_delay=None, fail_at=None, cap_now=None):
|
8921
|
+
schedule_priority = priority
|
8922
|
+
"""
|
8923
|
+
wait for any or all of the given state values are met
|
8924
|
+
|
8925
|
+
Parameters
|
8926
|
+
----------
|
8927
|
+
cond : callable
|
8928
|
+
parameterless function that return True if wait is over
|
8929
|
+
|
8930
|
+
states : iterable
|
8931
|
+
specicies which states should trigger the cond to be checked
|
8932
|
+
|
8933
|
+
request_priority : float
|
8934
|
+
put component in waiters queue according to the given priority (deafult 0)
|
8935
|
+
|
8936
|
+
priority : float
|
8937
|
+
priority of the fail event
|
8938
|
+
|
8939
|
+
default: 0
|
8940
|
+
|
8941
|
+
if a component has the same time on the event list, this component is sorted accoring to
|
8942
|
+
the priority.
|
8943
|
+
|
8944
|
+
urgent : bool
|
8945
|
+
urgency indicator
|
8946
|
+
|
8947
|
+
if False (default), the component will be scheduled
|
8948
|
+
behind all other components scheduled
|
8949
|
+
for the same time and priority
|
8950
|
+
|
8951
|
+
if True, the component will be scheduled
|
8952
|
+
in front of all components scheduled
|
8953
|
+
for the same time and priority
|
8954
|
+
|
8955
|
+
fail_at : float or distribution
|
8956
|
+
time out
|
8957
|
+
|
8958
|
+
if the wait is not honored before fail_at,
|
8959
|
+
the wait will be cancelled and the
|
8960
|
+
parameter failed will be set.
|
8961
|
+
|
8962
|
+
if not specified, the wait will not time out.
|
8963
|
+
|
8964
|
+
if distribution, the distribution is sampled
|
8965
|
+
|
8966
|
+
fail_delay : float or distribution
|
8967
|
+
time out
|
8968
|
+
|
8969
|
+
if the wait is not honored before now+fail_delay,
|
8970
|
+
the request will be cancelled and the
|
8971
|
+
parameter failed will be set.
|
8972
|
+
|
8973
|
+
if not specified, the wait will not time out.
|
8974
|
+
|
8975
|
+
if distribution, the distribution is sampled
|
8976
|
+
|
8977
|
+
mode : str preferred
|
8978
|
+
mode
|
8979
|
+
|
8980
|
+
will be used in trace and can be used in animations
|
8981
|
+
|
8982
|
+
if nothing specified, the mode will be unchanged.
|
8983
|
+
|
8984
|
+
also mode_time will be set to now, if mode is set.
|
8985
|
+
|
8986
|
+
cap_now : bool
|
8987
|
+
indicator whether times (fail_at, fail_duration) in the past are allowed. If, so now() will be used.
|
8988
|
+
default: sys.default_cap_now(), usualy False
|
8989
|
+
|
8990
|
+
Note
|
8991
|
+
----
|
8992
|
+
Not allowed for data components or main.
|
8993
|
+
|
8994
|
+
Only if yieldless is False: If to be used for the current component
|
8995
|
+
(which will be nearly always the case),
|
8996
|
+
use ``yield self.wait(...)``.
|
8997
|
+
|
8998
|
+
"""
|
8999
|
+
if self.status.value != current:
|
9000
|
+
self._checkisnotdata()
|
9001
|
+
self._checkisnotmain()
|
9002
|
+
self._remove()
|
9003
|
+
self._check_fail()
|
9004
|
+
|
9005
|
+
self._failed = False
|
9006
|
+
|
9007
|
+
if fail_at is None:
|
9008
|
+
if fail_delay is None:
|
9009
|
+
scheduled_time = inf
|
9010
|
+
else:
|
9011
|
+
if fail_delay == inf:
|
9012
|
+
scheduled_time = inf
|
9013
|
+
else:
|
9014
|
+
fail_delay = self.env.spec_to_duration(fail_delay)
|
9015
|
+
scheduled_time = self.env._now + fail_delay
|
9016
|
+
else:
|
9017
|
+
if fail_delay is None:
|
9018
|
+
fail_at = self.env.spec_to_time(fail_at)
|
9019
|
+
scheduled_time = fail_at + self.env._offset
|
9020
|
+
else:
|
9021
|
+
raise ValueError("both fail_at and fail_delay specified")
|
9022
|
+
|
9023
|
+
self.set_mode(mode)
|
9024
|
+
|
9025
|
+
self._cond = cond # add test ***
|
9026
|
+
for state in states:
|
9027
|
+
self._waits.append((state, None, None))
|
9028
|
+
if priority is None:
|
9029
|
+
self.enter(state._waiters)
|
9030
|
+
else:
|
9031
|
+
self.enter_sorted(state._waiters, priority)
|
9032
|
+
|
9033
|
+
if not self._waits:
|
9034
|
+
raise TypeError("no states specified")
|
9035
|
+
|
9036
|
+
self._remaining_duration = scheduled_time - self.env._now
|
9037
|
+
|
9038
|
+
self._trywait()
|
9039
|
+
|
9040
|
+
if self._waits:
|
9041
|
+
self.status._value = waiting
|
9042
|
+
self._reschedule(scheduled_time, schedule_priority, urgent, "wait_for", cap_now)
|
9043
|
+
else:
|
9044
|
+
return
|
9045
|
+
|
9046
|
+
def wait(
|
9047
|
+
self,
|
9048
|
+
*args,
|
9049
|
+
fail_at: float = None,
|
9050
|
+
fail_delay: float = None,
|
9051
|
+
all: bool = False,
|
9052
|
+
mode: Any = None,
|
9053
|
+
urgent: bool = False,
|
9054
|
+
request_priority: float = 0,
|
9055
|
+
schedule_priority: float = 0,
|
9056
|
+
cap_now: bool = None,
|
9057
|
+
) -> None:
|
8833
9058
|
"""
|
8834
9059
|
wait for any or all of the given state values are met
|
8835
9060
|
|
@@ -8845,6 +9070,8 @@ by adding:
|
|
8845
9070
|
be added to the tail of
|
8846
9071
|
the waiters queue
|
8847
9072
|
|
9073
|
+
request_priority : float
|
9074
|
+
put component in waiters queue according to the given priority (default 0)
|
8848
9075
|
|
8849
9076
|
priority : float
|
8850
9077
|
priority of the fail event
|
@@ -8970,16 +9197,7 @@ by adding:
|
|
8970
9197
|
--> waits for s1.value()==True and s2.value==True
|
8971
9198
|
|
8972
9199
|
"""
|
8973
|
-
|
8974
|
-
fail_delay = kwargs.pop("fail_delay", None)
|
8975
|
-
all = kwargs.pop("all", False)
|
8976
|
-
mode = kwargs.pop("mode", None)
|
8977
|
-
urgent = kwargs.pop("urgent", False)
|
8978
|
-
schedule_priority = kwargs.pop("priority", 0)
|
8979
|
-
cap_now = kwargs.pop("cap_now", None)
|
8980
|
-
|
8981
|
-
if kwargs:
|
8982
|
-
raise TypeError("wait() got an unexpected keyword argument '" + tuple(kwargs)[0] + "'")
|
9200
|
+
self._cond = None
|
8983
9201
|
|
8984
9202
|
if self.status.value != current:
|
8985
9203
|
self._checkisnotdata()
|
@@ -9010,7 +9228,7 @@ by adding:
|
|
9010
9228
|
|
9011
9229
|
for arg in args:
|
9012
9230
|
value = True
|
9013
|
-
priority =
|
9231
|
+
priority = request_priority
|
9014
9232
|
if isinstance(arg, State):
|
9015
9233
|
state = arg
|
9016
9234
|
elif isinstance(arg, (tuple, list)):
|
@@ -9018,10 +9236,8 @@ by adding:
|
|
9018
9236
|
if not isinstance(state, State):
|
9019
9237
|
raise TypeError("incorrect specifier", arg)
|
9020
9238
|
if len(arg) >= 2:
|
9021
|
-
|
9239
|
+
priority = arg[1]
|
9022
9240
|
if len(arg) >= 3:
|
9023
|
-
priority = arg[2]
|
9024
|
-
if len(arg) >= 4:
|
9025
9241
|
raise TypeError("incorrect specifier", arg)
|
9026
9242
|
else:
|
9027
9243
|
raise TypeError("incorrect specifier", arg)
|
@@ -9057,37 +9273,40 @@ by adding:
|
|
9057
9273
|
def _trywait(self):
|
9058
9274
|
if self.status.value == interrupted:
|
9059
9275
|
return False
|
9060
|
-
if self.
|
9061
|
-
honored =
|
9062
|
-
for state, value, valuetype in self._waits:
|
9063
|
-
if valuetype == 0:
|
9064
|
-
if value != state._value:
|
9065
|
-
honored = False
|
9066
|
-
break
|
9067
|
-
elif valuetype == 1:
|
9068
|
-
if not eval(value.replace("$", "state._value")):
|
9069
|
-
honored = False
|
9070
|
-
break
|
9071
|
-
elif valuetype == 2:
|
9072
|
-
if not value(state._value, self, state):
|
9073
|
-
honored = False
|
9074
|
-
break
|
9075
|
-
|
9276
|
+
if self._cond:
|
9277
|
+
honored = self._cond()
|
9076
9278
|
else:
|
9077
|
-
|
9078
|
-
|
9079
|
-
|
9080
|
-
if
|
9081
|
-
|
9082
|
-
|
9083
|
-
|
9084
|
-
|
9085
|
-
|
9086
|
-
|
9087
|
-
|
9088
|
-
|
9089
|
-
|
9090
|
-
|
9279
|
+
if self._wait_all:
|
9280
|
+
honored = True
|
9281
|
+
for state, value, valuetype in self._waits:
|
9282
|
+
if valuetype == 0:
|
9283
|
+
if value != state._value:
|
9284
|
+
honored = False
|
9285
|
+
break
|
9286
|
+
elif valuetype == 1:
|
9287
|
+
if not eval(value.replace("$", "state._value")):
|
9288
|
+
honored = False
|
9289
|
+
break
|
9290
|
+
elif valuetype == 2:
|
9291
|
+
if not value(state._value, self, state):
|
9292
|
+
honored = False
|
9293
|
+
break
|
9294
|
+
|
9295
|
+
else:
|
9296
|
+
honored = False
|
9297
|
+
for state, value, valuetype in self._waits:
|
9298
|
+
if valuetype == 0:
|
9299
|
+
if value == state._value:
|
9300
|
+
honored = True
|
9301
|
+
break
|
9302
|
+
elif valuetype == 1:
|
9303
|
+
if eval(value.replace("$", str(state._value))):
|
9304
|
+
honored = True
|
9305
|
+
break
|
9306
|
+
elif valuetype == 2:
|
9307
|
+
if value(state._value, self, state):
|
9308
|
+
honored = True
|
9309
|
+
break
|
9091
9310
|
|
9092
9311
|
if honored:
|
9093
9312
|
for s, _, _ in self._waits:
|
@@ -9446,7 +9665,7 @@ by adding:
|
|
9446
9665
|
index += 1
|
9447
9666
|
return index
|
9448
9667
|
|
9449
|
-
def enter(self, q: "Queue") -> "Component":
|
9668
|
+
def enter(self, q: "Queue", priority: float = None) -> "Component":
|
9450
9669
|
"""
|
9451
9670
|
enters a queue at the tail
|
9452
9671
|
|
@@ -9462,8 +9681,17 @@ by adding:
|
|
9462
9681
|
or 0 if queue is empty
|
9463
9682
|
"""
|
9464
9683
|
self._checknotinqueue(q)
|
9465
|
-
priority
|
9466
|
-
|
9684
|
+
if priority is None:
|
9685
|
+
priority = q._tail.predecessor.priority
|
9686
|
+
Qmember().insert_in_front_of(q._tail, self, q, priority)
|
9687
|
+
else:
|
9688
|
+
if q._length >= 1 and priority < q._head.successor.priority: # direct enter component that's smaller than the rest
|
9689
|
+
m2 = q._head.successor
|
9690
|
+
else:
|
9691
|
+
m2 = q._tail
|
9692
|
+
while (m2.predecessor != q._head) and (m2.predecessor.priority > priority):
|
9693
|
+
m2 = m2.predecessor
|
9694
|
+
Qmember().insert_in_front_of(m2, self, q, priority)
|
9467
9695
|
return self
|
9468
9696
|
|
9469
9697
|
def enter_at_head(self, q: "Queue") -> "Component":
|
@@ -9542,22 +9770,14 @@ by adding:
|
|
9542
9770
|
q : Queue
|
9543
9771
|
queue to enter
|
9544
9772
|
|
9545
|
-
priority:
|
9773
|
+
priority: float
|
9546
9774
|
priority in the queue
|
9547
9775
|
|
9548
9776
|
Note
|
9549
9777
|
----
|
9550
9778
|
The component is placed just before the first component with a priority > given priority
|
9551
9779
|
"""
|
9552
|
-
self.
|
9553
|
-
if q._length >= 1 and priority < q._head.successor.priority: # direct enter component that's smaller than the rest
|
9554
|
-
m2 = q._head.successor
|
9555
|
-
else:
|
9556
|
-
m2 = q._tail
|
9557
|
-
while (m2.predecessor != q._head) and (m2.predecessor.priority > priority):
|
9558
|
-
m2 = m2.predecessor
|
9559
|
-
Qmember().insert_in_front_of(m2, self, q, priority)
|
9560
|
-
return self
|
9780
|
+
return self.enter(q, priority)
|
9561
9781
|
|
9562
9782
|
def leave(self, q: "Queue" = None) -> "Component":
|
9563
9783
|
"""
|
@@ -9623,7 +9843,7 @@ by adding:
|
|
9623
9843
|
q : Queue
|
9624
9844
|
queue where the component belongs to
|
9625
9845
|
|
9626
|
-
priority :
|
9846
|
+
priority : float
|
9627
9847
|
priority in queue
|
9628
9848
|
|
9629
9849
|
if omitted, no change
|
@@ -10765,8 +10985,8 @@ class Environment:
|
|
10765
10985
|
s = "view("
|
10766
10986
|
items = []
|
10767
10987
|
for prop in props.split():
|
10768
|
-
items.append(f"{getattr(self.view,prop)(t):.4f}")
|
10769
|
-
print("view(" + (",".join(f"{prop}={getattr(self.view,prop)(t):.4f}" for prop in props.split())) + f") # t={t:.4f}")
|
10988
|
+
items.append(f"{getattr(self.view, prop)(t):.4f}")
|
10989
|
+
print("view(" + (",".join(f"{prop}={getattr(self.view, prop)(t):.4f}" for prop in props.split())) + f") # t={t:.4f}")
|
10770
10990
|
|
10771
10991
|
def _bind(self, tkinter_event, func):
|
10772
10992
|
self.root.bind(tkinter_event, func)
|
@@ -10882,7 +11102,7 @@ class Environment:
|
|
10882
11102
|
ao.label = "fovy" if prop == "field_of_view_y" else prop
|
10883
11103
|
|
10884
11104
|
ao = AnimateText(
|
10885
|
-
text=lambda arg, t: f"{getattr(self.view,arg.prop)(t):11.3f}",
|
11105
|
+
text=lambda arg, t: f"{getattr(self.view, arg.prop)(t):11.3f}",
|
10886
11106
|
x=5 + i * 80 + 70,
|
10887
11107
|
y=top,
|
10888
11108
|
font="calibri",
|
@@ -11724,7 +11944,7 @@ class Environment:
|
|
11724
11944
|
format="GIF",
|
11725
11945
|
)
|
11726
11946
|
else:
|
11727
|
-
for _ in range(2):
|
11947
|
+
for _ in range(2): # normally runs only once
|
11728
11948
|
try:
|
11729
11949
|
self._images[0].save(
|
11730
11950
|
self._video_name,
|
@@ -11736,7 +11956,7 @@ class Environment:
|
|
11736
11956
|
)
|
11737
11957
|
break
|
11738
11958
|
except ValueError: # prevent bug in Python 3.13
|
11739
|
-
self._images=[image.convert("RGB") for image in self._images]
|
11959
|
+
self._images = [image.convert("RGB") for image in self._images]
|
11740
11960
|
|
11741
11961
|
else:
|
11742
11962
|
if PythonInExcel or AnacondaCode:
|
@@ -11752,9 +11972,8 @@ class Environment:
|
|
11752
11972
|
format="GIF",
|
11753
11973
|
)
|
11754
11974
|
else:
|
11755
|
-
for _ in range(2):
|
11975
|
+
for _ in range(2): # normally runs only once
|
11756
11976
|
try:
|
11757
|
-
|
11758
11977
|
self._images[0].save(
|
11759
11978
|
self._video_name,
|
11760
11979
|
disposal=2,
|
@@ -11765,7 +11984,7 @@ class Environment:
|
|
11765
11984
|
optimize=False,
|
11766
11985
|
)
|
11767
11986
|
except ValueError: # prevent bug in Python 3.13
|
11768
|
-
self._images=[image.convert("RGB") for image in self._images]
|
11987
|
+
self._images = [image.convert("RGB") for image in self._images]
|
11769
11988
|
|
11770
11989
|
del self._images
|
11771
11990
|
elif self._video_out == "png":
|
@@ -11829,7 +12048,6 @@ class Environment:
|
|
11829
12048
|
Number of 1/30 second long frames to be inserted
|
11830
12049
|
"""
|
11831
12050
|
|
11832
|
-
|
11833
12051
|
if self._video_out is None:
|
11834
12052
|
raise ValueError("video not set")
|
11835
12053
|
if isinstance(image, (Path, str)):
|
@@ -11855,7 +12073,6 @@ class Environment:
|
|
11855
12073
|
open_cv_image = cv2.cvtColor(numpy.array(image), cv2.COLOR_RGB2BGR)
|
11856
12074
|
self._video_out.write(open_cv_image)
|
11857
12075
|
|
11858
|
-
|
11859
12076
|
def _save_frame(self):
|
11860
12077
|
self._exclude_from_animation = "not in video"
|
11861
12078
|
image = self._capture_image("RGBA", self._video_mode)
|
@@ -12790,7 +13007,7 @@ class Environment:
|
|
12790
13007
|
self.animation_parameters(synced=value, animate=None)
|
12791
13008
|
return self._synced
|
12792
13009
|
|
12793
|
-
def minimized(self, value: bool=None)-> bool:
|
13010
|
+
def minimized(self, value: bool = None) -> bool:
|
12794
13011
|
"""
|
12795
13012
|
minimized
|
12796
13013
|
|
@@ -13058,7 +13275,7 @@ class Environment:
|
|
13058
13275
|
"""
|
13059
13276
|
return self._current_component
|
13060
13277
|
|
13061
|
-
def run(self, duration: float = None, till: float = None, priority:
|
13278
|
+
def run(self, duration: float = None, till: float = None, priority: float = inf, urgent: bool = False, cap_now: bool = None):
|
13062
13279
|
"""
|
13063
13280
|
start execution of the simulation
|
13064
13281
|
|
@@ -13198,7 +13415,6 @@ class Environment:
|
|
13198
13415
|
self._t = self.animation_start_time
|
13199
13416
|
else:
|
13200
13417
|
self._t = self.animation_start_time + ((time.time() - self.animation_start_clocktime) * self._speed)
|
13201
|
-
|
13202
13418
|
while self.peek() < self._t:
|
13203
13419
|
self.step()
|
13204
13420
|
if not (self.running and self._animate):
|
@@ -15004,7 +15220,7 @@ class Environment:
|
|
15004
15220
|
self.pause_at = inf
|
15005
15221
|
|
15006
15222
|
if self.pause_at is None:
|
15007
|
-
sg.popup(
|
15223
|
+
sg.popup("Pause not valid")
|
15008
15224
|
else:
|
15009
15225
|
if "-PAUSE-EACH-" in self._ui_keys:
|
15010
15226
|
pause_each_str = values["-PAUSE-EACH-"]
|
@@ -15023,7 +15239,7 @@ class Environment:
|
|
15023
15239
|
self.pause_at = None
|
15024
15240
|
|
15025
15241
|
if self.pause_at is None:
|
15026
|
-
sg.popup(
|
15242
|
+
sg.popup("Pause interval not valid")
|
15027
15243
|
else:
|
15028
15244
|
if self.pause_at > self.t() * 0.99999999:
|
15029
15245
|
if "-ANIMATE-" in self._ui_keys:
|
@@ -15626,29 +15842,49 @@ class Animate2dBase(DynamicClass):
|
|
15626
15842
|
im = Image.new("RGBA", (int(totwidth + 0.1 * fontsize), int(totheight)), (0, 0, 0, 0))
|
15627
15843
|
imwidth, imheight = im.size
|
15628
15844
|
draw = ImageDraw.Draw(im)
|
15629
|
-
|
15845
|
+
ypos = 0
|
15846
|
+
now_color = textcolor
|
15630
15847
|
for line, width in zip(lines, widths):
|
15631
15848
|
if line:
|
15632
|
-
|
15633
|
-
|
15634
|
-
|
15635
|
-
|
15636
|
-
|
15637
|
-
|
15638
|
-
|
15639
|
-
|
15640
|
-
|
15641
|
-
|
15642
|
-
|
15643
|
-
|
15644
|
-
|
15645
|
-
|
15646
|
-
|
15647
|
-
|
15648
|
-
|
15649
|
-
pix[x, y] = (textcolor[0], textcolor[1], textcolor[2], pix[x, y][3])
|
15849
|
+
if "\033[" in line: # ANSI
|
15850
|
+
xpos = 0.1 * fontsize
|
15851
|
+
while line:
|
15852
|
+
for ansi, rgb in _ANSI_to_rgb.items():
|
15853
|
+
if line.startswith(ansi):
|
15854
|
+
if rgb:
|
15855
|
+
now_color = rgb
|
15856
|
+
else:
|
15857
|
+
now_color = textcolor
|
15858
|
+
line = line[len(ansi) :]
|
15859
|
+
break
|
15860
|
+
else:
|
15861
|
+
c = line[0]
|
15862
|
+
draw.text(xy=(xpos, ypos), text=c, font=font, fill=now_color)
|
15863
|
+
charwidth = font.getbbox(c)[2]
|
15864
|
+
xpos += charwidth
|
15865
|
+
line = line[1:]
|
15650
15866
|
|
15651
|
-
|
15867
|
+
else:
|
15868
|
+
draw.text(xy=(0.1 * fontsize, ypos), text=line, font=font, fill=now_color)
|
15869
|
+
|
15870
|
+
ypos += lineheight
|
15871
|
+
# # this code is to correct a bug in the rendering of text,
|
15872
|
+
# # leaving a kind of shadow around the text
|
15873
|
+
# del draw
|
15874
|
+
# if textcolor[:3] != (0, 0, 0): # black is ok
|
15875
|
+
# if False and has_numpy():
|
15876
|
+
# arr = numpy.asarray(im).copy()
|
15877
|
+
# arr[:, :, 0] = textcolor[0]
|
15878
|
+
# arr[:, :, 1] = textcolor[1]
|
15879
|
+
# arr[:, :, 2] = textcolor[2]
|
15880
|
+
# im = Image.fromarray(numpy.uint8(arr))
|
15881
|
+
# else:
|
15882
|
+
# pix = im.load()
|
15883
|
+
# for y in range(imheight):
|
15884
|
+
# for x in range(imwidth):
|
15885
|
+
# pix[x, y] = (textcolor[0], textcolor[1], textcolor[2], pix[x, y][3])
|
15886
|
+
|
15887
|
+
# # end of code to correct bug
|
15652
15888
|
|
15653
15889
|
self.imwidth, self.imheight = im.size
|
15654
15890
|
self.heightA = heightA
|
@@ -23508,7 +23744,7 @@ class State:
|
|
23508
23744
|
self.value.tally(value_after)
|
23509
23745
|
self._trywait()
|
23510
23746
|
|
23511
|
-
def _trywait(self, max=inf):
|
23747
|
+
def _trywait(self, max=inf): # this _trywait of a state
|
23512
23748
|
mx = self._waiters._head.successor
|
23513
23749
|
while mx != self._waiters._tail:
|
23514
23750
|
c = mx.component
|
@@ -25426,7 +25662,6 @@ class Animate3dObj(Animate3dBase):
|
|
25426
25662
|
global visualization
|
25427
25663
|
global pyglet
|
25428
25664
|
|
25429
|
-
|
25430
25665
|
self.x = x
|
25431
25666
|
self.y = y
|
25432
25667
|
self.z = z
|
@@ -25451,12 +25686,12 @@ class Animate3dObj(Animate3dBase):
|
|
25451
25686
|
try:
|
25452
25687
|
import pywavefront
|
25453
25688
|
except ImportError:
|
25454
|
-
pywavefront=None
|
25455
|
-
|
25689
|
+
pywavefront = None
|
25690
|
+
|
25456
25691
|
try:
|
25457
|
-
import pyglet
|
25692
|
+
import pyglet # this is a requirement for visualization!
|
25458
25693
|
except ImportError:
|
25459
|
-
pyglet=None
|
25694
|
+
pyglet = None
|
25460
25695
|
|
25461
25696
|
from pywavefront import visualization
|
25462
25697
|
|
@@ -25466,7 +25701,7 @@ class Animate3dObj(Animate3dBase):
|
|
25466
25701
|
global pyglet
|
25467
25702
|
if pywavefront is None:
|
25468
25703
|
raise ImportError("Animate3dObj requires pywavefront. Not found")
|
25469
|
-
if pyglet is None:
|
25704
|
+
if pyglet is None:
|
25470
25705
|
raise ImportError("Animate3dObj requires pyglet. Not found")
|
25471
25706
|
|
25472
25707
|
obj_filename = Path(self.filename(t))
|
@@ -26976,8 +27211,6 @@ def getfont(fontname, fontsize):
|
|
26976
27211
|
return getfont.lookup[(fontname, fontsize)]
|
26977
27212
|
else:
|
26978
27213
|
getfont.lookup = {}
|
26979
|
-
if fontname == "":
|
26980
|
-
a = 1
|
26981
27214
|
if isinstance(fontname, str):
|
26982
27215
|
fontlist1 = [fontname]
|
26983
27216
|
else:
|
@@ -27222,7 +27455,7 @@ def can_animate3d(try_only: bool = True) -> bool:
|
|
27222
27455
|
glut.glutInit()
|
27223
27456
|
except OpenGL.error.NullFunctionError:
|
27224
27457
|
raise ImportError("Installed OpenGL does not support glut. Try 'pip install OpenGL-glut' or see the salabim documentation")
|
27225
|
-
|
27458
|
+
|
27226
27459
|
return True
|
27227
27460
|
else:
|
27228
27461
|
if try_only:
|
@@ -27585,7 +27818,7 @@ def set_environment_aliases():
|
|
27585
27818
|
return # do not set when using Sphinx build!
|
27586
27819
|
|
27587
27820
|
for name, obj in list(globals().items()):
|
27588
|
-
if (not name.startswith("_") or name in ("_Trajectory", "_Distribution")) and name != "yieldless" and name != "Environment":
|
27821
|
+
if (not name.startswith("_") or name in ("_Trajectory", "_Distribution")) and name != "yieldless" and name != "Environment" and not hasattr(Environment,name):
|
27589
27822
|
if inspect.isclass(obj) and obj.__module__ == Environment.__module__:
|
27590
27823
|
if issubclass(obj, Exception):
|
27591
27824
|
exec(f"Environment.{name}={name}")
|
@@ -0,0 +1,10 @@
|
|
1
|
+
salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
|
2
|
+
salabim/LICENSE.txt,sha256=eTPlcDJz4G0096Qv-wfMjm1Wxbd4ilDlsYg5rN4HjWQ,1106
|
3
|
+
salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
|
4
|
+
salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
|
5
|
+
salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
|
6
|
+
salabim/salabim.py,sha256=G7w62bzTMLNDN8paZkvYUA3MmvfPhwouL2nc7SLNO2A,1123563
|
7
|
+
salabim-25.0.0.dist-info/METADATA,sha256=m_Yl7oewus-EsdjjsC1OpAwGJy7UE5UCNFBgOa4jfO4,3457
|
8
|
+
salabim-25.0.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
+
salabim-25.0.0.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
|
10
|
+
salabim-25.0.0.dist-info/RECORD,,
|
salabim-24.0.17.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
|
2
|
-
salabim/LICENSE.txt,sha256=qHlBa-POyexatCxDTjSKMlYtkBFQDn9lu-YV_1L6V0U,1106
|
3
|
-
salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
|
4
|
-
salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
|
5
|
-
salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
|
6
|
-
salabim/salabim.py,sha256=-u4nEmv4kHg0im9ZC_yL31bvXNBuJNWFLuBf_FYhCpE,1115641
|
7
|
-
salabim-24.0.17.dist-info/METADATA,sha256=1ZtRaop2jcG8-kTv1-viuasV_UnkXuT46PuvMG6uGDs,3458
|
8
|
-
salabim-24.0.17.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
9
|
-
salabim-24.0.17.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
|
10
|
-
salabim-24.0.17.dist-info/RECORD,,
|
File without changes
|