salabim 24.0.17__tar.gz → 25.0.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. {salabim-24.0.17 → salabim-25.0.0}/PKG-INFO +2 -2
  2. {salabim-24.0.17 → salabim-25.0.0}/pyproject.toml +36 -30
  3. {salabim-24.0.17 → salabim-25.0.0}/salabim/LICENSE.txt +1 -1
  4. {salabim-24.0.17 → salabim-25.0.0}/salabim/salabim.py +373 -140
  5. {salabim-24.0.17 → salabim-25.0.0}/salabim.egg-info/PKG-INFO +2 -2
  6. {salabim-24.0.17 → salabim-25.0.0}/README.md +0 -0
  7. {salabim-24.0.17 → salabim-25.0.0}/salabim/DejaVuSansMono.ttf +0 -0
  8. {salabim-24.0.17 → salabim-25.0.0}/salabim/__init__.py +0 -0
  9. {salabim-24.0.17 → salabim-25.0.0}/salabim/calibri.ttf +0 -0
  10. {salabim-24.0.17 → salabim-25.0.0}/salabim/mplus-1m-regular.ttf +0 -0
  11. {salabim-24.0.17 → salabim-25.0.0}/salabim.egg-info/SOURCES.txt +0 -0
  12. {salabim-24.0.17 → salabim-25.0.0}/salabim.egg-info/dependency_links.txt +0 -0
  13. {salabim-24.0.17 → salabim-25.0.0}/salabim.egg-info/top_level.txt +0 -0
  14. {salabim-24.0.17 → salabim-25.0.0}/setup.cfg +0 -0
  15. {salabim-24.0.17 → salabim-25.0.0}/tests/test salabim.py +0 -0
  16. {salabim-24.0.17 → salabim-25.0.0}/tests/test_cap_now.py +0 -0
  17. {salabim-24.0.17 → salabim-25.0.0}/tests/test_componentgenerator.py +0 -0
  18. {salabim-24.0.17 → salabim-25.0.0}/tests/test_datetime.py +0 -0
  19. {salabim-24.0.17 → salabim-25.0.0}/tests/test_distributions.py +0 -0
  20. {salabim-24.0.17 → salabim-25.0.0}/tests/test_misc.py +0 -0
  21. {salabim-24.0.17 → salabim-25.0.0}/tests/test_monitor.py +0 -0
  22. {salabim-24.0.17 → salabim-25.0.0}/tests/test_process.py +0 -0
  23. {salabim-24.0.17 → salabim-25.0.0}/tests/test_queue.py +0 -0
  24. {salabim-24.0.17 → salabim-25.0.0}/tests/test_state.py +0 -0
  25. {salabim-24.0.17 → salabim-25.0.0}/tests/test_store.py +0 -0
  26. {salabim-24.0.17 → salabim-25.0.0}/tests/test_timeunit.py +0 -0
@@ -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
@@ -1,30 +1,36 @@
1
- [build-system]
2
- requires = ["setuptools"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "salabim"
7
- authors = [
8
- {name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com"}
9
- ]
10
- description = "salabim - discrete event simulation in Python"
11
- version = "24.0.17"
12
- readme = "README.md"
13
- requires-python = ">=3.7"
14
- dependencies = [
15
- ]
16
- classifiers = [
17
- "Development Status :: 5 - Production/Stable",
18
- "License :: OSI Approved :: MIT License",
19
- "Programming Language :: Python :: 3 :: Only"
20
- ]
21
- [project.urls]
22
- Homepage = "https://salabim.org"
23
- Repository = "https://github.com/salabim/salabim"
24
-
25
-
26
- [tool.setuptools]
27
- packages = ["salabim"]
28
-
29
- [tool.setuptools.package-data]
30
- "*" = ["*.ttf", "*.txt"]
1
+ [build-system]
2
+ requires = [
3
+ "setuptools",
4
+ ]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "salabim"
9
+ authors = [
10
+ { name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com" },
11
+ ]
12
+ description = "salabim - discrete event simulation in Python"
13
+ version = "25.0.0"
14
+ readme = "README.md"
15
+ requires-python = ">=3.7"
16
+ dependencies = []
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://salabim.org"
25
+ Repository = "https://github.com/salabim/salabim"
26
+
27
+ [tool.setuptools]
28
+ packages = [
29
+ "salabim",
30
+ ]
31
+
32
+ [tool.setuptools.package-data]
33
+ "*" = [
34
+ "*.ttf",
35
+ "*.txt",
36
+ ]
@@ -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
@@ -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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes