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 CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (C) 2017-2024 Ruud van der Ham, ruud@salabim.org
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__ = "24.0.17"
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: Any) -> "Queue":
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: type that can be compared with other priorities in the queue
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.enter(store._from_store_requesters)
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 component will be scheduled
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 component will be scheduled
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.enter(store._to_store_requesters)
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(self, *args, **kwargs) -> None:
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 tail of
8398
- the requesters queue
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 = inf
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.enter_sorted(r._requesters, priority)
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 wait(self, *args, **kwargs) -> None:
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
- fail_at = kwargs.pop("fail_at", None)
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 = None
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
- value = arg[1]
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._wait_all:
9061
- honored = True
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
- honored = False
9078
- for state, value, valuetype in self._waits:
9079
- if valuetype == 0:
9080
- if value == state._value:
9081
- honored = True
9082
- break
9083
- elif valuetype == 1:
9084
- if eval(value.replace("$", str(state._value))):
9085
- honored = True
9086
- break
9087
- elif valuetype == 2:
9088
- if value(state._value, self, state):
9089
- honored = True
9090
- break
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 = q._tail.predecessor.priority
9466
- Qmember().insert_in_front_of(q._tail, self, q, priority)
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: type that can be compared with other priorities in the queue
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._checknotinqueue(q)
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 : type that can be compared with other priorities in the queue
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): # normally runs only once
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): # normally runs only once
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: Any = inf, urgent: bool = False, cap_now: bool = None):
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(f"Pause not valid")
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(f"Pause interval not valid")
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
- pos = 0
15845
+ ypos = 0
15846
+ now_color = textcolor
15630
15847
  for line, width in zip(lines, widths):
15631
15848
  if line:
15632
- draw.text(xy=(0.1 * fontsize, pos), text=line, font=font, fill=textcolor)
15633
-
15634
- pos += lineheight
15635
- # this code is to correct a bug in the rendering of text,
15636
- # leaving a kind of shadow around the text
15637
- del draw
15638
- if textcolor[:3] != (0, 0, 0): # black is ok
15639
- if False and has_numpy():
15640
- arr = numpy.asarray(im).copy()
15641
- arr[:, :, 0] = textcolor[0]
15642
- arr[:, :, 1] = textcolor[1]
15643
- arr[:, :, 2] = textcolor[2]
15644
- im = Image.fromarray(numpy.uint8(arr))
15645
- else:
15646
- pix = im.load()
15647
- for y in range(imheight):
15648
- for x in range(imwidth):
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
- # end of code to correct bug
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 # this is a requirement for visualization!
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}")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: salabim
3
- Version: 24.0.17
3
+ Version: 25.0.0
4
4
  Summary: salabim - discrete event simulation in Python
5
5
  Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
6
  Project-URL: Homepage, https://salabim.org
@@ -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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.5.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,