LiSE version 0.16
The 0.15 series of LiSE releases was all about dynamic unloading, which wasn't much fun to show off because it just meant LiSE would use less memory. And time travel was slower in some cases.
0.16 is about multiprocess parallelism, which turned out to be easier to implement than I expected. Here's how you find a bunch of paths all at once in a LiSE sim:
import random
import networkx as nx
from LiSE import Engine
with Engine(random_seed=69105) as eng:
random.seed(69105)
grid: nx.Graph = nx.grid_2d_graph(100, 100)
for node in list(grid):
if random.random() < 0.1:
grid.remove_node(node)
elif random.random() < 0.01:
grid.add_node(f"{node}_inhabitant", location=node)
phys = eng.new_character("physical", grid)
@eng.function
def find_path_somewhere(node):
from networkx.algorithms import astar_path
from math import sqrt
x, y = node.location.name
destx = 100 - int(x)
desty = 100 - int(y)
while (destx, desty) not in node.character.place:
if destx < 99:
destx += 1
elif desty < 99:
destx = 0
desty += 1
else:
destx = desty = 0
ret = astar_path(
node.character,
node.location.name,
(destx, desty),
lambda a, b: sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2),
)
node.engine.debug(
f"{node.name}'s shortest path to {destx, desty} is {ret}"
)
return ret
@phys.rule
def go_places(char):
from time import monotonic
from networkx.exception import NetworkXNoPath
def log_as_completed(fut):
try:
char.engine.debug(
f"Got path for {fut.thing.name}: {fut.result()}"
)
except NetworkXNoPath:
char.engine.debug(f"No path for {fut.thing.name}")
futs = []
for thing in char.thing.values():
fut = char.engine.submit(
char.engine.function.find_path_somewhere, thing
)
fut.thing = thing
fut.add_done_callback(log_as_completed)
futs.append(fut)
with char.engine.batch():
for fut in futs:
try:
result = fut.result()
thing = fut.thing
start = monotonic()
thing.follow_path(result, check=False)
char.engine.debug(
f"followed path for thing {thing.name} in {monotonic() - start:.2} seconds"
)
except NetworkXNoPath:
char.engine.debug(
f"got no path for thing {fut.thing.name}"
)
continue
@go_places.trigger
def turn_one_only(char):
return char.engine.turn == 1
