|
9 | 9 | import random |
10 | 10 | from concurrent import futures |
11 | 11 | import logging |
| 12 | +import atexit |
| 13 | +import weakref |
12 | 14 |
|
13 | 15 | import numpy as np |
14 | 16 | from glm import ivec3 |
@@ -76,16 +78,16 @@ def __init__( |
76 | 78 | self._worldSlice: Optional[WorldSlice] = None |
77 | 79 | self._worldSliceDecay: Optional[np.ndarray] = None |
78 | 80 |
|
| 81 | + ref = weakref.ref(self) |
| 82 | + |
| 83 | + # Use a lambda to allow unregistering only one instance |
| 84 | + self._cleanup = lambda: cleanup_at_exit(ref) |
| 85 | + atexit.register(self._cleanup) |
| 86 | + |
79 | 87 |
|
80 | 88 | def __del__(self): |
81 | 89 | """Cleans up this Editor instance""" |
82 | | - # awaits any pending buffer flush futures and shuts down the buffer flush executor |
83 | | - self.multithreading = False |
84 | | - # Flush any remaining blocks in the buffer. |
85 | | - # This is purposefully done *after* disabling multithreading! This __del__ may be called at |
86 | | - # interpreter shutdown, and it appears that scheduling a new future at that point fails with |
87 | | - # "RuntimeError: cannot schedule new futures after shutdown" even if the executor has not |
88 | | - # actually shut down yet. For safety, the last buffer flush must be done on the main thread. |
| 90 | + atexit.unregister(self._cleanup) |
89 | 91 | self.flushBuffer() |
90 | 92 |
|
91 | 93 |
|
@@ -594,3 +596,10 @@ def pushTransform(self, transformLike: Optional[TransformLike] = None): |
594 | 596 | yield |
595 | 597 | finally: |
596 | 598 | self.transform = originalTransform |
| 599 | + |
| 600 | +# Flush buffers if the system is shutting down. |
| 601 | +# Do this via an atexit handler instead of the destructor because it needs |
| 602 | +# to run before necessary modules are torns down. |
| 603 | +def cleanup_at_exit(ref: weakref.ref): |
| 604 | + if obj := ref(): |
| 605 | + obj.flushBuffer() |
0 commit comments