|
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