salabim 24.0.18__tar.gz → 25.0.1__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.18 → salabim-25.0.1}/PKG-INFO +2 -2
  2. {salabim-24.0.18 → salabim-25.0.1}/pyproject.toml +36 -30
  3. {salabim-24.0.18 → salabim-25.0.1}/salabim/LICENSE.txt +1 -1
  4. {salabim-24.0.18 → salabim-25.0.1}/salabim/salabim.py +191 -114
  5. {salabim-24.0.18 → salabim-25.0.1}/salabim.egg-info/PKG-INFO +2 -2
  6. {salabim-24.0.18 → salabim-25.0.1}/README.md +0 -0
  7. {salabim-24.0.18 → salabim-25.0.1}/salabim/DejaVuSansMono.ttf +0 -0
  8. {salabim-24.0.18 → salabim-25.0.1}/salabim/__init__.py +0 -0
  9. {salabim-24.0.18 → salabim-25.0.1}/salabim/calibri.ttf +0 -0
  10. {salabim-24.0.18 → salabim-25.0.1}/salabim/mplus-1m-regular.ttf +0 -0
  11. {salabim-24.0.18 → salabim-25.0.1}/salabim.egg-info/SOURCES.txt +0 -0
  12. {salabim-24.0.18 → salabim-25.0.1}/salabim.egg-info/dependency_links.txt +0 -0
  13. {salabim-24.0.18 → salabim-25.0.1}/salabim.egg-info/top_level.txt +0 -0
  14. {salabim-24.0.18 → salabim-25.0.1}/setup.cfg +0 -0
  15. {salabim-24.0.18 → salabim-25.0.1}/tests/test salabim.py +0 -0
  16. {salabim-24.0.18 → salabim-25.0.1}/tests/test_cap_now.py +0 -0
  17. {salabim-24.0.18 → salabim-25.0.1}/tests/test_componentgenerator.py +0 -0
  18. {salabim-24.0.18 → salabim-25.0.1}/tests/test_datetime.py +0 -0
  19. {salabim-24.0.18 → salabim-25.0.1}/tests/test_distributions.py +0 -0
  20. {salabim-24.0.18 → salabim-25.0.1}/tests/test_misc.py +0 -0
  21. {salabim-24.0.18 → salabim-25.0.1}/tests/test_monitor.py +0 -0
  22. {salabim-24.0.18 → salabim-25.0.1}/tests/test_process.py +0 -0
  23. {salabim-24.0.18 → salabim-25.0.1}/tests/test_queue.py +0 -0
  24. {salabim-24.0.18 → salabim-25.0.1}/tests/test_state.py +0 -0
  25. {salabim-24.0.18 → salabim-25.0.1}/tests/test_store.py +0 -0
  26. {salabim-24.0.18 → salabim-25.0.1}/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.18
3
+ Version: 25.0.1
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.18"
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.1"
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.18"
11
-
10
+ __version__ = "25.0.1"
12
11
  import heapq
13
12
  import random
14
13
  import time
@@ -42,8 +41,8 @@ import urllib.request
42
41
  import urllib.error
43
42
  import base64
44
43
  import zipfile
45
-
46
44
  from pathlib import Path
45
+
47
46
 
48
47
  from typing import Any, Union, Iterable, Tuple, List, Callable, TextIO, Dict, Set, Type, Hashable, Optional
49
48
 
@@ -61,6 +60,46 @@ Chromebook = "penguin" in platform.uname()
61
60
  PythonInExcel = not ("__file__" in globals())
62
61
  AnacondaCode = sys.platform == "emscripten"
63
62
 
63
+ _color_name_to_ANSI = dict(
64
+ dark_black="\033[0;30m",
65
+ dark_red="\033[0;31m",
66
+ dark_green="\033[0;32m",
67
+ dark_yellow="\033[0;33m",
68
+ dark_blue="\033[0;34m",
69
+ dark_magenta="\033[0;35m",
70
+ dark_cyan="\033[0;36m",
71
+ dark_white="\033[0;37m",
72
+ black="\033[1;30m",
73
+ red="\033[1;31m",
74
+ green="\033[1;32m",
75
+ yellow="\033[1;33m",
76
+ blue="\033[1;34m",
77
+ magenta="\033[1;35m",
78
+ cyan="\033[1;36m",
79
+ white="\033[1;37m",
80
+ reset="\033[0m",
81
+ )
82
+ _ANSI_to_rgb = {
83
+ "\033[1;30m": (51, 51, 51),
84
+ "\033[1;31m": (255, 0, 0),
85
+ "\033[1;32m": (0, 255, 0),
86
+ "\033[1;33m": (255, 255, 0),
87
+ "\033[1;34m": (0, 178, 255),
88
+ "\033[1;35m": (255, 0, 255),
89
+ "\033[1;36m": (0, 255, 255),
90
+ "\033[1;37m": (255, 255, 255),
91
+ "\033[0;30m": (76, 76, 76),
92
+ "\033[0;31m": (178, 0, 0),
93
+ "\033[0;32m": (0, 178, 0),
94
+ "\033[0;33m": (178, 178, 0),
95
+ "\033[0;34m": (0, 89, 255),
96
+ "\033[0;35m": (178, 0, 178),
97
+ "\033[0;36m": (0, 178, 178),
98
+ "\033[0;37m": (178, 178, 178),
99
+ "\033[0m": (),
100
+ }
101
+
102
+ ANSI=types.SimpleNamespace(**_color_name_to_ANSI)
64
103
 
65
104
  def a_log(*args):
66
105
  if not hasattr(a_log, "a_logfile_name"):
@@ -4866,7 +4905,7 @@ class Queue:
4866
4905
  """
4867
4906
  return getattr(self, "_sequence_number", 1)
4868
4907
 
4869
- def add(self, component: "Component") -> "Queue":
4908
+ def add(self, component: "Component", priority: float = None) -> "Queue":
4870
4909
  """
4871
4910
  adds a component to the tail of a queue
4872
4911
 
@@ -4877,18 +4916,23 @@ class Queue:
4877
4916
 
4878
4917
  may not be member of the queue yet
4879
4918
 
4919
+ priority : float
4920
+ if None (default), add to the tail of the queue
4921
+
4922
+ otherwise, put after the last component with the same priority
4923
+
4880
4924
  Note
4881
4925
  ----
4882
- the priority will be set to
4926
+ if prioority is None, the priority will be set to
4883
4927
  the priority of the tail of the queue, if any
4884
4928
  or 0 if queue is empty
4885
4929
 
4886
4930
  This method is equivalent to append()
4887
4931
  """
4888
- component.enter(self)
4932
+ component.enter(self, priority)
4889
4933
  return self
4890
4934
 
4891
- def append(self, component: "Component") -> "Queue":
4935
+ def append(self, component: "Component", priority: float = None) -> "Queue":
4892
4936
  """
4893
4937
  appends a component to the tail of a queue
4894
4938
 
@@ -4899,9 +4943,14 @@ class Queue:
4899
4943
 
4900
4944
  may not be member of the queue yet
4901
4945
 
4946
+ priority : float
4947
+ if None (default), add to the tail of the queue
4948
+
4949
+ otherwise, put after the last component with the same priority
4950
+
4902
4951
  Note
4903
4952
  ----
4904
- the priority will be set to
4953
+ if priority is None, the priority will be set to
4905
4954
  the priority of the tail of the queue, if any
4906
4955
  or 0 if queue is empty
4907
4956
 
@@ -5013,7 +5062,7 @@ class Queue:
5013
5062
  component.enter_behind(self, poscomponent)
5014
5063
  return self
5015
5064
 
5016
- def add_sorted(self, component: "Component", priority: Any) -> "Queue":
5065
+ def add_sorted(self, component: "Component", priority: float) -> "Queue":
5017
5066
  """
5018
5067
  adds a component to a queue, according to the priority
5019
5068
 
@@ -5024,7 +5073,7 @@ class Queue:
5024
5073
 
5025
5074
  may not be member of the queue yet
5026
5075
 
5027
- priority: type that can be compared with other priorities in the queue
5076
+ priority: float
5028
5077
  priority in the queue
5029
5078
 
5030
5079
  Note
@@ -7339,7 +7388,6 @@ by adding at the end:
7339
7388
  self.status.reset(monitor=monitor, stats_only=stats_only)
7340
7389
  self.mode.reset(monitor=monitor, stats_only=stats_only)
7341
7390
 
7342
-
7343
7391
  def register(self, registry: List) -> "Component":
7344
7392
  """
7345
7393
  registers the component in the registry
@@ -8073,6 +8121,7 @@ by adding:
8073
8121
  self,
8074
8122
  store: Union["Store", Iterable],
8075
8123
  filter: Callable = lambda c: True,
8124
+ request_priority: Any = 0,
8076
8125
  fail_priority: float = 0,
8077
8126
  urgent: bool = True,
8078
8127
  fail_at: float = None,
@@ -8096,6 +8145,9 @@ by adding:
8096
8145
 
8097
8146
  default: lambda c: True (i.e. always return True)
8098
8147
 
8148
+ request_priority: float
8149
+ put component in to_store_requesters according to the given priority (default 0)
8150
+
8099
8151
  fail_priority : float
8100
8152
  priority of the fail event
8101
8153
 
@@ -8236,7 +8288,7 @@ by adding:
8236
8288
 
8237
8289
  self._from_stores = from_stores
8238
8290
  for store in from_stores:
8239
- self.enter(store._from_store_requesters)
8291
+ self.enter_sorted(store._from_store_requesters, priority=request_priority)
8240
8292
  self.status._value = requesting
8241
8293
  self._from_store_item = None
8242
8294
  self._from_store_filter = filter
@@ -8249,6 +8301,7 @@ by adding:
8249
8301
  self,
8250
8302
  store: Union["Store", Iterable],
8251
8303
  item: "Component",
8304
+ request_priority: float = None,
8252
8305
  priority: float = 0,
8253
8306
  fail_priority: float = 0,
8254
8307
  urgent: bool = True,
@@ -8268,6 +8321,9 @@ by adding:
8268
8321
  item: Component
8269
8322
  component to put to store
8270
8323
 
8324
+ request_priority: float
8325
+ put component in to_store_requesters according to the given priority (default 0)
8326
+
8271
8327
  fail_priority : float
8272
8328
  priority of the fail event
8273
8329
 
@@ -8279,11 +8335,11 @@ by adding:
8279
8335
  urgent : bool
8280
8336
  urgency indicator
8281
8337
 
8282
- if False (default), the component will be scheduled
8338
+ if False (default), the fail event will be scheduled
8283
8339
  behind all other components scheduled
8284
8340
  for the same time and priority
8285
8341
 
8286
- if True, the component will be scheduled
8342
+ if True, the fail event will be scheduled
8287
8343
  in front of all components scheduled
8288
8344
  for the same time and priority
8289
8345
 
@@ -8381,7 +8437,7 @@ by adding:
8381
8437
  return
8382
8438
 
8383
8439
  for store in to_stores:
8384
- self.enter(store._to_store_requesters)
8440
+ self.enter_sorted(store._to_store_requesters, priority=request_priority)
8385
8441
  self.status._value = requesting
8386
8442
  self._to_store_item = item
8387
8443
  self._to_store_priority = priority
@@ -8407,7 +8463,19 @@ by adding:
8407
8463
  for store in self._from_stores:
8408
8464
  store.rescan()
8409
8465
 
8410
- def request(self, *args, **kwargs) -> None:
8466
+ def request(
8467
+ self,
8468
+ *args,
8469
+ fail_at: float = None,
8470
+ fail_delay: float = None,
8471
+ mode: Any = None,
8472
+ urgent: bool = False,
8473
+ request_priority: float = 0,
8474
+ schedule_priority: float = 0,
8475
+ cap_now: bool = None,
8476
+ oneof: bool = False,
8477
+ called_from: str = "request",
8478
+ ) -> None:
8411
8479
  """
8412
8480
  request from a resource or resources
8413
8481
 
@@ -8417,9 +8485,12 @@ by adding:
8417
8485
  - resource, where quantity=1, priority=tail of requesters queue
8418
8486
  - tuples/list containing a resource, a quantity and optionally a priority.
8419
8487
  if the priority is not specified, the request
8420
- for the resource be added to the tail of
8421
- the requesters queue
8488
+ for the resource will be added according to the request_priority parameter
8489
+
8490
+ request_priority: float
8491
+ (may be overridden by the priority parameter in the arg sequence)
8422
8492
 
8493
+ put component requesters according to the given priority (default 0)
8423
8494
 
8424
8495
  priority : float
8425
8496
  priority of the fail event
@@ -8522,17 +8593,7 @@ by adding:
8522
8593
  --> requests 1 from r1, r2 or r3
8523
8594
 
8524
8595
  """
8525
- fail_at = kwargs.pop("fail_at", None)
8526
- fail_delay = kwargs.pop("fail_delay", None)
8527
- mode = kwargs.pop("mode", None)
8528
- urgent = kwargs.pop("urgent", False)
8529
- schedule_priority = kwargs.pop("priority", 0)
8530
- cap_now = kwargs.pop("cap_now", None)
8531
- oneof = kwargs.pop("oneof", False)
8532
- called_from = kwargs.pop("called_from", "request")
8533
8596
  self.oneof_request = oneof
8534
- if kwargs:
8535
- raise TypeError(called_from + "() got an unexpected keyword argument '" + tuple(kwargs)[0] + "'")
8536
8597
 
8537
8598
  if self.status.value != current:
8538
8599
  self._checkisnotdata()
@@ -8565,7 +8626,7 @@ by adding:
8565
8626
  return
8566
8627
  for arg in args:
8567
8628
  q = 1
8568
- priority = inf
8629
+ priority = request_priority
8569
8630
  if isinstance(arg, Resource):
8570
8631
  r = arg
8571
8632
  elif isinstance(arg, (tuple, list)):
@@ -8603,7 +8664,7 @@ by adding:
8603
8664
  if self.oneof_request:
8604
8665
  addstring += " (oneof)"
8605
8666
 
8606
- self.enter_sorted(r._requesters, priority)
8667
+ self.enter(r._requesters, priority)
8607
8668
  if self.env._trace:
8608
8669
  self.env.print_trace("", "", self.name(), req_text + r.name() + addstring)
8609
8670
 
@@ -8852,7 +8913,7 @@ by adding:
8852
8913
  for r in list(self._claims):
8853
8914
  self._release(r)
8854
8915
 
8855
- def wait_for(self,cond, states, priority=0, urgent=False, mode=None,fail_delay=None, fail_at=None, cap_now=None):
8916
+ def wait_for(self, cond, states, request_priority=0, priority=0, urgent=False, mode=None, fail_delay=None, fail_at=None, cap_now=None):
8856
8917
  schedule_priority = priority
8857
8918
  """
8858
8919
  wait for any or all of the given state values are met
@@ -8865,6 +8926,9 @@ by adding:
8865
8926
  states : iterable
8866
8927
  specicies which states should trigger the cond to be checked
8867
8928
 
8929
+ request_priority : float
8930
+ put component in waiters queue according to the given priority (deafult 0)
8931
+
8868
8932
  priority : float
8869
8933
  priority of the fail event
8870
8934
 
@@ -8954,9 +9018,9 @@ by adding:
8954
9018
 
8955
9019
  self.set_mode(mode)
8956
9020
 
8957
- self._cond=cond # add test ***
9021
+ self._cond = cond # add test ***
8958
9022
  for state in states:
8959
- self._waits.append((state, None,None))
9023
+ self._waits.append((state, None, None))
8960
9024
  if priority is None:
8961
9025
  self.enter(state._waiters)
8962
9026
  else:
@@ -8975,9 +9039,18 @@ by adding:
8975
9039
  else:
8976
9040
  return
8977
9041
 
8978
-
8979
-
8980
- def wait(self, *args, **kwargs) -> None:
9042
+ def wait(
9043
+ self,
9044
+ *args,
9045
+ fail_at: float = None,
9046
+ fail_delay: float = None,
9047
+ all: bool = False,
9048
+ mode: Any = None,
9049
+ urgent: bool = False,
9050
+ request_priority: float = 0,
9051
+ schedule_priority: float = 0,
9052
+ cap_now: bool = None,
9053
+ ) -> None:
8981
9054
  """
8982
9055
  wait for any or all of the given state values are met
8983
9056
 
@@ -8993,6 +9066,8 @@ by adding:
8993
9066
  be added to the tail of
8994
9067
  the waiters queue
8995
9068
 
9069
+ request_priority : float
9070
+ put component in waiters queue according to the given priority (default 0)
8996
9071
 
8997
9072
  priority : float
8998
9073
  priority of the fail event
@@ -9118,18 +9193,7 @@ by adding:
9118
9193
  --> waits for s1.value()==True and s2.value==True
9119
9194
 
9120
9195
  """
9121
- fail_at = kwargs.pop("fail_at", None)
9122
- fail_delay = kwargs.pop("fail_delay", None)
9123
- all = kwargs.pop("all", False)
9124
- mode = kwargs.pop("mode", None)
9125
- urgent = kwargs.pop("urgent", False)
9126
- schedule_priority = kwargs.pop("priority", 0)
9127
- cap_now = kwargs.pop("cap_now", None)
9128
-
9129
- self._cond=None
9130
-
9131
- if kwargs:
9132
- raise TypeError("wait() got an unexpected keyword argument '" + tuple(kwargs)[0] + "'")
9196
+ self._cond = None
9133
9197
 
9134
9198
  if self.status.value != current:
9135
9199
  self._checkisnotdata()
@@ -9160,7 +9224,7 @@ by adding:
9160
9224
 
9161
9225
  for arg in args:
9162
9226
  value = True
9163
- priority = None
9227
+ priority = request_priority
9164
9228
  if isinstance(arg, State):
9165
9229
  state = arg
9166
9230
  elif isinstance(arg, (tuple, list)):
@@ -9168,10 +9232,8 @@ by adding:
9168
9232
  if not isinstance(state, State):
9169
9233
  raise TypeError("incorrect specifier", arg)
9170
9234
  if len(arg) >= 2:
9171
- value = arg[1]
9235
+ priority = arg[1]
9172
9236
  if len(arg) >= 3:
9173
- priority = arg[2]
9174
- if len(arg) >= 4:
9175
9237
  raise TypeError("incorrect specifier", arg)
9176
9238
  else:
9177
9239
  raise TypeError("incorrect specifier", arg)
@@ -9205,7 +9267,6 @@ by adding:
9205
9267
  return
9206
9268
 
9207
9269
  def _trywait(self):
9208
-
9209
9270
  if self.status.value == interrupted:
9210
9271
  return False
9211
9272
  if self._cond:
@@ -9600,7 +9661,7 @@ by adding:
9600
9661
  index += 1
9601
9662
  return index
9602
9663
 
9603
- def enter(self, q: "Queue") -> "Component":
9664
+ def enter(self, q: "Queue", priority: float = None) -> "Component":
9604
9665
  """
9605
9666
  enters a queue at the tail
9606
9667
 
@@ -9616,8 +9677,17 @@ by adding:
9616
9677
  or 0 if queue is empty
9617
9678
  """
9618
9679
  self._checknotinqueue(q)
9619
- priority = q._tail.predecessor.priority
9620
- Qmember().insert_in_front_of(q._tail, self, q, priority)
9680
+ if priority is None:
9681
+ priority = q._tail.predecessor.priority
9682
+ Qmember().insert_in_front_of(q._tail, self, q, priority)
9683
+ else:
9684
+ if q._length >= 1 and priority < q._head.successor.priority: # direct enter component that's smaller than the rest
9685
+ m2 = q._head.successor
9686
+ else:
9687
+ m2 = q._tail
9688
+ while (m2.predecessor != q._head) and (m2.predecessor.priority > priority):
9689
+ m2 = m2.predecessor
9690
+ Qmember().insert_in_front_of(m2, self, q, priority)
9621
9691
  return self
9622
9692
 
9623
9693
  def enter_at_head(self, q: "Queue") -> "Component":
@@ -9696,22 +9766,14 @@ by adding:
9696
9766
  q : Queue
9697
9767
  queue to enter
9698
9768
 
9699
- priority: type that can be compared with other priorities in the queue
9769
+ priority: float
9700
9770
  priority in the queue
9701
9771
 
9702
9772
  Note
9703
9773
  ----
9704
9774
  The component is placed just before the first component with a priority > given priority
9705
9775
  """
9706
- self._checknotinqueue(q)
9707
- if q._length >= 1 and priority < q._head.successor.priority: # direct enter component that's smaller than the rest
9708
- m2 = q._head.successor
9709
- else:
9710
- m2 = q._tail
9711
- while (m2.predecessor != q._head) and (m2.predecessor.priority > priority):
9712
- m2 = m2.predecessor
9713
- Qmember().insert_in_front_of(m2, self, q, priority)
9714
- return self
9776
+ return self.enter(q, priority)
9715
9777
 
9716
9778
  def leave(self, q: "Queue" = None) -> "Component":
9717
9779
  """
@@ -9777,7 +9839,7 @@ by adding:
9777
9839
  q : Queue
9778
9840
  queue where the component belongs to
9779
9841
 
9780
- priority : type that can be compared with other priorities in the queue
9842
+ priority : float
9781
9843
  priority in queue
9782
9844
 
9783
9845
  if omitted, no change
@@ -10919,8 +10981,8 @@ class Environment:
10919
10981
  s = "view("
10920
10982
  items = []
10921
10983
  for prop in props.split():
10922
- items.append(f"{getattr(self.view,prop)(t):.4f}")
10923
- print("view(" + (",".join(f"{prop}={getattr(self.view,prop)(t):.4f}" for prop in props.split())) + f") # t={t:.4f}")
10984
+ items.append(f"{getattr(self.view, prop)(t):.4f}")
10985
+ print("view(" + (",".join(f"{prop}={getattr(self.view, prop)(t):.4f}" for prop in props.split())) + f") # t={t:.4f}")
10924
10986
 
10925
10987
  def _bind(self, tkinter_event, func):
10926
10988
  self.root.bind(tkinter_event, func)
@@ -11036,7 +11098,7 @@ class Environment:
11036
11098
  ao.label = "fovy" if prop == "field_of_view_y" else prop
11037
11099
 
11038
11100
  ao = AnimateText(
11039
- text=lambda arg, t: f"{getattr(self.view,arg.prop)(t):11.3f}",
11101
+ text=lambda arg, t: f"{getattr(self.view, arg.prop)(t):11.3f}",
11040
11102
  x=5 + i * 80 + 70,
11041
11103
  y=top,
11042
11104
  font="calibri",
@@ -11878,7 +11940,7 @@ class Environment:
11878
11940
  format="GIF",
11879
11941
  )
11880
11942
  else:
11881
- for _ in range(2): # normally runs only once
11943
+ for _ in range(2): # normally runs only once
11882
11944
  try:
11883
11945
  self._images[0].save(
11884
11946
  self._video_name,
@@ -11890,7 +11952,7 @@ class Environment:
11890
11952
  )
11891
11953
  break
11892
11954
  except ValueError: # prevent bug in Python 3.13
11893
- self._images=[image.convert("RGB") for image in self._images]
11955
+ self._images = [image.convert("RGB") for image in self._images]
11894
11956
 
11895
11957
  else:
11896
11958
  if PythonInExcel or AnacondaCode:
@@ -11906,9 +11968,8 @@ class Environment:
11906
11968
  format="GIF",
11907
11969
  )
11908
11970
  else:
11909
- for _ in range(2): # normally runs only once
11971
+ for _ in range(2): # normally runs only once
11910
11972
  try:
11911
-
11912
11973
  self._images[0].save(
11913
11974
  self._video_name,
11914
11975
  disposal=2,
@@ -11919,7 +11980,7 @@ class Environment:
11919
11980
  optimize=False,
11920
11981
  )
11921
11982
  except ValueError: # prevent bug in Python 3.13
11922
- self._images=[image.convert("RGB") for image in self._images]
11983
+ self._images = [image.convert("RGB") for image in self._images]
11923
11984
 
11924
11985
  del self._images
11925
11986
  elif self._video_out == "png":
@@ -11983,7 +12044,6 @@ class Environment:
11983
12044
  Number of 1/30 second long frames to be inserted
11984
12045
  """
11985
12046
 
11986
-
11987
12047
  if self._video_out is None:
11988
12048
  raise ValueError("video not set")
11989
12049
  if isinstance(image, (Path, str)):
@@ -12009,7 +12069,6 @@ class Environment:
12009
12069
  open_cv_image = cv2.cvtColor(numpy.array(image), cv2.COLOR_RGB2BGR)
12010
12070
  self._video_out.write(open_cv_image)
12011
12071
 
12012
-
12013
12072
  def _save_frame(self):
12014
12073
  self._exclude_from_animation = "not in video"
12015
12074
  image = self._capture_image("RGBA", self._video_mode)
@@ -12944,7 +13003,7 @@ class Environment:
12944
13003
  self.animation_parameters(synced=value, animate=None)
12945
13004
  return self._synced
12946
13005
 
12947
- def minimized(self, value: bool=None)-> bool:
13006
+ def minimized(self, value: bool = None) -> bool:
12948
13007
  """
12949
13008
  minimized
12950
13009
 
@@ -13212,7 +13271,7 @@ class Environment:
13212
13271
  """
13213
13272
  return self._current_component
13214
13273
 
13215
- def run(self, duration: float = None, till: float = None, priority: Any = inf, urgent: bool = False, cap_now: bool = None):
13274
+ def run(self, duration: float = None, till: float = None, priority: float = inf, urgent: bool = False, cap_now: bool = None):
13216
13275
  """
13217
13276
  start execution of the simulation
13218
13277
 
@@ -13352,7 +13411,6 @@ class Environment:
13352
13411
  self._t = self.animation_start_time
13353
13412
  else:
13354
13413
  self._t = self.animation_start_time + ((time.time() - self.animation_start_clocktime) * self._speed)
13355
-
13356
13414
  while self.peek() < self._t:
13357
13415
  self.step()
13358
13416
  if not (self.running and self._animate):
@@ -15780,29 +15838,49 @@ class Animate2dBase(DynamicClass):
15780
15838
  im = Image.new("RGBA", (int(totwidth + 0.1 * fontsize), int(totheight)), (0, 0, 0, 0))
15781
15839
  imwidth, imheight = im.size
15782
15840
  draw = ImageDraw.Draw(im)
15783
- pos = 0
15841
+ ypos = 0
15842
+ now_color = textcolor
15784
15843
  for line, width in zip(lines, widths):
15785
15844
  if line:
15786
- draw.text(xy=(0.1 * fontsize, pos), text=line, font=font, fill=textcolor)
15787
-
15788
- pos += lineheight
15789
- # this code is to correct a bug in the rendering of text,
15790
- # leaving a kind of shadow around the text
15791
- del draw
15792
- if textcolor[:3] != (0, 0, 0): # black is ok
15793
- if False and has_numpy():
15794
- arr = numpy.asarray(im).copy()
15795
- arr[:, :, 0] = textcolor[0]
15796
- arr[:, :, 1] = textcolor[1]
15797
- arr[:, :, 2] = textcolor[2]
15798
- im = Image.fromarray(numpy.uint8(arr))
15799
- else:
15800
- pix = im.load()
15801
- for y in range(imheight):
15802
- for x in range(imwidth):
15803
- pix[x, y] = (textcolor[0], textcolor[1], textcolor[2], pix[x, y][3])
15845
+ if "\033[" in line: # ANSI
15846
+ xpos = 0.1 * fontsize
15847
+ while line:
15848
+ for ansi, rgb in _ANSI_to_rgb.items():
15849
+ if line.startswith(ansi):
15850
+ if rgb:
15851
+ now_color = rgb
15852
+ else:
15853
+ now_color = textcolor
15854
+ line = line[len(ansi) :]
15855
+ break
15856
+ else:
15857
+ c = line[0]
15858
+ draw.text(xy=(xpos, ypos), text=c, font=font, fill=now_color)
15859
+ charwidth = font.getbbox(c)[2]
15860
+ xpos += charwidth
15861
+ line = line[1:]
15804
15862
 
15805
- # end of code to correct bug
15863
+ else:
15864
+ draw.text(xy=(0.1 * fontsize, ypos), text=line, font=font, fill=now_color)
15865
+
15866
+ ypos += lineheight
15867
+ # # this code is to correct a bug in the rendering of text,
15868
+ # # leaving a kind of shadow around the text
15869
+ # del draw
15870
+ # if textcolor[:3] != (0, 0, 0): # black is ok
15871
+ # if False and has_numpy():
15872
+ # arr = numpy.asarray(im).copy()
15873
+ # arr[:, :, 0] = textcolor[0]
15874
+ # arr[:, :, 1] = textcolor[1]
15875
+ # arr[:, :, 2] = textcolor[2]
15876
+ # im = Image.fromarray(numpy.uint8(arr))
15877
+ # else:
15878
+ # pix = im.load()
15879
+ # for y in range(imheight):
15880
+ # for x in range(imwidth):
15881
+ # pix[x, y] = (textcolor[0], textcolor[1], textcolor[2], pix[x, y][3])
15882
+
15883
+ # # end of code to correct bug
15806
15884
 
15807
15885
  self.imwidth, self.imheight = im.size
15808
15886
  self.heightA = heightA
@@ -23662,7 +23740,7 @@ class State:
23662
23740
  self.value.tally(value_after)
23663
23741
  self._trywait()
23664
23742
 
23665
- def _trywait(self, max=inf): # this _trywait of a state
23743
+ def _trywait(self, max=inf): # this _trywait of a state
23666
23744
  mx = self._waiters._head.successor
23667
23745
  while mx != self._waiters._tail:
23668
23746
  c = mx.component
@@ -25580,7 +25658,6 @@ class Animate3dObj(Animate3dBase):
25580
25658
  global visualization
25581
25659
  global pyglet
25582
25660
 
25583
-
25584
25661
  self.x = x
25585
25662
  self.y = y
25586
25663
  self.z = z
@@ -25605,12 +25682,12 @@ class Animate3dObj(Animate3dBase):
25605
25682
  try:
25606
25683
  import pywavefront
25607
25684
  except ImportError:
25608
- pywavefront=None
25609
-
25685
+ pywavefront = None
25686
+
25610
25687
  try:
25611
- import pyglet # this is a requirement for visualization!
25688
+ import pyglet # this is a requirement for visualization!
25612
25689
  except ImportError:
25613
- pyglet=None
25690
+ pyglet = None
25614
25691
 
25615
25692
  from pywavefront import visualization
25616
25693
 
@@ -25620,7 +25697,7 @@ class Animate3dObj(Animate3dBase):
25620
25697
  global pyglet
25621
25698
  if pywavefront is None:
25622
25699
  raise ImportError("Animate3dObj requires pywavefront. Not found")
25623
- if pyglet is None:
25700
+ if pyglet is None:
25624
25701
  raise ImportError("Animate3dObj requires pyglet. Not found")
25625
25702
 
25626
25703
  obj_filename = Path(self.filename(t))
@@ -27374,7 +27451,7 @@ def can_animate3d(try_only: bool = True) -> bool:
27374
27451
  glut.glutInit()
27375
27452
  except OpenGL.error.NullFunctionError:
27376
27453
  raise ImportError("Installed OpenGL does not support glut. Try 'pip install OpenGL-glut' or see the salabim documentation")
27377
-
27454
+
27378
27455
  return True
27379
27456
  else:
27380
27457
  if try_only:
@@ -27737,7 +27814,7 @@ def set_environment_aliases():
27737
27814
  return # do not set when using Sphinx build!
27738
27815
 
27739
27816
  for name, obj in list(globals().items()):
27740
- if (not name.startswith("_") or name in ("_Trajectory", "_Distribution")) and name != "yieldless" and name != "Environment":
27817
+ if (not name.startswith("_") or name in ("_Trajectory", "_Distribution")) and name != "yieldless" and name != "Environment" and not hasattr(Environment,name):
27741
27818
  if inspect.isclass(obj) and obj.__module__ == Environment.__module__:
27742
27819
  if issubclass(obj, Exception):
27743
27820
  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.18
3
+ Version: 25.0.1
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