OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge commits / 195f1b7

Procedural texture baking and dynamic SHM growth (v0.15.0)
Bake procedural textures (Noise, Voronoi, Gradient, etc.) to images via Cycles, then send through existing VT tile pipeline. ColorRamp chains are traced upstream recursively. Material parameter changes trigger automatic re-bake via depsgraph shading update detection. SHM regions now grow dynamically when mesh blobs exceed capacity. Blender estimates blob size before flushing, resizes via ftruncate, and notifies Godot to remap. Fixes crash on 500k+ tri objects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
195f1b716c07387509d7ab4bce25390eed54555b
by plankatron <fleeky@gmail.com> · 2026-03-04T02:06:26.000Z

Changed files (7) +341 -34

blender_addon/__init__.py +191 -28
……@@ -11,7 +11,7 @@ Usage:
1111
1212import bpy
1313
14ADDON_VERSION = "0.14.1"
14ADDON_VERSION = "0.15.0"
1515
1616# Support hot-reload (F3 > Reload Scripts)
1717if "shm_protocol" in locals():
……@@ -39,6 +39,8 @@ _detector: ChangeDetector | None = None
3939_baker: TileBaker | None = None
4040_timer_running = False
4141_timer_generation = 0 # Incremented on each connect to invalidate stale timers
42_sent_procedurals: set = set() # "obj_name:mat_name" already baked+registered
43_baked_images: dict = {} # "obj_name:mat_name" → baked image name
4244
4345SHM_NAME = "/blender_bridge"
4446SHM_SIZE = 24 * 1024 * 1024 # 24 MB (8 MB region 0 + 8 MB region 1 + 8 MB tile ring)
……@@ -57,16 +59,20 @@ def _make_poll_timer(generation: int):
5759 return _poll_timer
5860
5961
60def _request_texture_registrations():
61 """Scan Blender scene for materials with image textures and request VT registration."""
62 global _conn, _baker
62def _scan_material_textures():
63 """Scan Blender scene for materials with textures and request VT registration.
64
65 Handles image textures (existing pipeline), procedural textures (bake first),
66 and ColorRamp chains (trace upstream recursively).
67 """
68 global _conn, _baker, _sent_procedurals, _baked_images
6369
6470 if _conn is None or _baker is None:
6571 return
6672
6773 import bpy
6874
69 requested = set()
75 image_requested = set()
7076 for obj in bpy.data.objects:
7177 if obj.type != "MESH" or obj.active_material is None:
7278 continue
……@@ -74,25 +80,166 @@ def _request_texture_registrations():
7480 if not mat.use_nodes or not mat.node_tree:
7581 continue
7682 for node in mat.node_tree.nodes:
77 if node.type == "BSDF_PRINCIPLED":
78 # Check if Base Color has an image texture connected
79 base_color_input = node.inputs.get("Base Color")
80 if base_color_input is None or not base_color_input.is_linked:
81 continue
82 for link in base_color_input.links:
83 from_node = link.from_node
84 if from_node.type == "TEX_IMAGE" and from_node.image is not None:
85 img = from_node.image
86 img_name = img.name
87 if img_name not in requested and img_name not in _baker._texture_ids:
88 requested.add(img_name)
89 img_w, img_h = img.size[0], img.size[1]
90 msg = f"request_texture:{img_name},{img_w},{img_h}"
91 _conn.send_message(msg.encode())
92 print(f"BlenderBridge: Requesting texture registration for '{img_name}' ({img_w}x{img_h})")
93
94 if requested:
95 print(f"BlenderBridge: Requested {len(requested)} texture(s)")
83 if node.type != "BSDF_PRINCIPLED":
84 continue
85 base_color = node.inputs.get("Base Color")
86 if base_color is None or not base_color.is_linked:
87 continue
88 for link in base_color.links:
89 _handle_texture_node(obj, mat, link.from_node, image_requested)
90
91 if image_requested:
92 print(f"BlenderBridge: Requested {len(image_requested)} texture(s)")
93
94
95_PROCEDURAL_TYPES = {
96 "TEX_NOISE", "TEX_VORONOI", "TEX_GRADIENT", "TEX_CHECKER",
97 "TEX_WAVE", "TEX_BRICK", "TEX_MAGIC", "TEX_MUSGRAVE",
98 "TEX_WHITE_NOISE",
99}
100
101
102def _handle_texture_node(obj, mat, from_node, image_requested):
103 """Route a texture node to the appropriate handler."""
104 global _conn, _baker, _sent_procedurals, _baked_images
105
106 # Image textures → existing VT pipeline (no bake)
107 if from_node.type == "TEX_IMAGE" and from_node.image is not None:
108 img = from_node.image
109 img_name = img.name
110 if img_name not in image_requested and img_name not in _baker._texture_ids:
111 image_requested.add(img_name)
112 img_w, img_h = img.size[0], img.size[1]
113 msg = f"request_texture:{img_name},{img_w},{img_h}"
114 _conn.send_message(msg.encode())
115 print(f"BlenderBridge: Requesting image texture '{img_name}' ({img_w}x{img_h})")
116 return
117
118 # ColorRamp → trace upstream for the actual texture node
119 if from_node.type == "VALTORGB":
120 fac_input = from_node.inputs.get("Fac")
121 if fac_input and fac_input.is_linked:
122 for inner_link in fac_input.links:
123 _handle_texture_node(obj, mat, inner_link.from_node, image_requested)
124 return
125
126 # Procedural textures → bake to image, then VT pipeline
127 if from_node.type in _PROCEDURAL_TYPES:
128 mat_key = f"{obj.name}:{mat.name}"
129 if mat_key in _sent_procedurals:
130 return
131 bake_img = _bake_material_to_image(obj, mat)
132 if bake_img is not None:
133 _baked_images[mat_key] = bake_img.name
134 _sent_procedurals.add(mat_key)
135 img_w, img_h = bake_img.size[0], bake_img.size[1]
136 if bake_img.name not in _baker._texture_ids:
137 msg = f"request_texture:{bake_img.name},{img_w},{img_h}"
138 _conn.send_message(msg.encode())
139 print(f"BlenderBridge: Baked procedural '{mat.name}' → requesting texture")
140
141
142TILE_RING_SIZE = 8 * 1024 * 1024 # 8 MB tile ring (fixed, does not grow)
143
144def _on_shm_resized(new_size: int, new_region_size: int):
145 """Called by ChangeDetector after SHM resize. Notifies Godot and updates tile baker."""
146 global _conn, _baker, _shm
147 if _conn is not None:
148 msg = f"shm_resized:{new_size},{new_region_size}"
149 _conn.send_message(msg.encode())
150 # Re-initialize tile baker with new ring buffer offset
151 if _baker is not None and _shm is not None:
152 tile_ring_offset = new_region_size * 2
153 tile_ring_size = new_size - tile_ring_offset
154 if tile_ring_size > 0:
155 _baker.init_ring_buffer(tile_ring_offset, tile_ring_size)
156 print(f"BlenderBridge: Tile ring moved to offset {tile_ring_offset}")
157
158
159BAKE_SIZE = 1024
160
161
162def _bake_material_to_image(obj, mat, size=BAKE_SIZE):
163 """Bake material's Base Color to an image using Cycles."""
164 import bpy
165
166 # Object must have UVs for baking
167 if not obj.data.uv_layers:
168 print(f"BlenderBridge: Skipping bake for '{obj.name}' — no UVs")
169 return None
170
171 scene = bpy.context.scene
172 old_engine = scene.render.engine
173
174 # Create or reuse bake target image
175 bake_name = f"_bb_bake_{mat.name}"
176 if bake_name in bpy.data.images:
177 bake_img = bpy.data.images[bake_name]
178 if bake_img.size[0] != size or bake_img.size[1] != size:
179 bpy.data.images.remove(bake_img)
180 bake_img = bpy.data.images.new(bake_name, size, size, alpha=True)
181 else:
182 bake_img = bpy.data.images.new(bake_name, size, size, alpha=True)
183
184 # Add temp Image Texture node as bake target (must be active node)
185 tree = mat.node_tree
186 bake_node = tree.nodes.new("ShaderNodeTexImage")
187 bake_node.image = bake_img
188 bake_node.name = "_bb_bake_target"
189 tree.nodes.active = bake_node
190
191 # Save selection state
192 old_active = bpy.context.view_layer.objects.active
193 old_selected = [o for o in bpy.context.selected_objects]
194
195 try:
196 # Use Cycles for baking
197 scene.render.engine = "CYCLES"
198 scene.cycles.samples = 4
199 scene.cycles.device = "CPU"
200
201 bpy.ops.object.select_all(action="DESELECT")
202 obj.select_set(True)
203 bpy.context.view_layer.objects.active = obj
204
205 bpy.ops.object.bake(type="DIFFUSE", pass_filter={"COLOR"}, margin=2)
206 print(f"BlenderBridge: Baked '{mat.name}' on '{obj.name}' ({size}x{size})")
207 return bake_img
208
209 except Exception as e:
210 print(f"BlenderBridge: Bake failed for '{mat.name}': {e}")
211 return None
212
213 finally:
214 # Clean up
215 tree.nodes.remove(bake_node)
216 scene.render.engine = old_engine
217 bpy.ops.object.select_all(action="DESELECT")
218 for o in old_selected:
219 if o and o.name in bpy.data.objects:
220 o.select_set(True)
221 if old_active and old_active.name in bpy.data.objects:
222 bpy.context.view_layer.objects.active = old_active
223
224
225def _rebake_procedurals():
226 """Re-bake all procedural textures when material parameters change."""
227 global _sent_procedurals, _baked_images, _baker
228 if _baker is None:
229 return
230
231 import bpy
232
233 for mat_key, img_name in _baked_images.items():
234 obj_name, mat_name = mat_key.split(":", 1)
235 obj = bpy.data.objects.get(obj_name)
236 mat = bpy.data.materials.get(mat_name)
237 if obj is None or mat is None:
238 continue
239 bake_img = _bake_material_to_image(obj, mat)
240 if bake_img is not None and bake_img.name in _baker._texture_ids:
241 _baker.bake_full_image(bake_img.name)
242 print(f"BlenderBridge: Re-baked procedural '{mat_name}'")
96243
97244
98245def _poll_timer_impl(generation: int):
……@@ -141,8 +288,8 @@ def _poll_timer_impl(generation: int):
141288 _detector.suppress(set(_importer._id_to_name.values()), 5)
142289 got_incoming = True
143290 print(f"BlenderBridge: Synced scene from Godot")
144 # Scan Blender scene for image textures and request VT registration
145 _request_texture_registrations()
291 # Scan Blender scene for textures (image + procedural) and request VT registration
292 _scan_material_textures()
146293 elif text in ("updated", "transform_update"):
147294 # Incremental update from Godot (region 0)
148295 if _importer is not None:
……@@ -186,6 +333,10 @@ def _poll_timer_impl(generation: int):
186333 for img_name in dirty_images:
187334 _baker.bake_full_image(img_name)
188335
336 # Re-bake procedural textures if shading changed (parameter edits)
337 if _detector is not None and _detector.had_shading_update():
338 _rebake_procedurals()
339
189340 # Flush any pending tile_ready messages from the baker
190341 if _baker is not None:
191342 for tile_msg in _baker.get_pending_messages():
……@@ -217,11 +368,12 @@ def _attempt_reconnect():
217368 _detector.unregister()
218369 _detector = ChangeDetector(_shm, MAX_OBJECTS)
219370 _detector.register()
371 _detector.set_resize_callback(_on_shm_resized)
220372
221373 # Re-initialize tile baker
222374 from .shm_protocol import REGION_SIZE
223375 tile_ring_offset = REGION_SIZE * 2
224 tile_ring_size = SHM_SIZE - tile_ring_offset
376 tile_ring_size = TILE_RING_SIZE
225377 _baker = TileBaker(_shm, tile_size=128, max_mips=8)
226378 _baker.init_ring_buffer(tile_ring_offset, tile_ring_size)
227379
……@@ -330,6 +482,10 @@ class BRIDGE_OT_connect(bpy.types.Operator):
330482
331483 def execute(self, context):
332484 global _shm, _conn, _importer, _detector, _baker, _timer_running, _timer_generation
485 global _sent_procedurals, _baked_images
486
487 _sent_procedurals = set()
488 _baked_images = {}
333489
334490 # Clean up any stale state from previous connection
335491 if _detector is not None:
……@@ -362,11 +518,12 @@ class BRIDGE_OT_connect(bpy.types.Operator):
362518 _importer = SceneImporter(_shm, MAX_OBJECTS)
363519 _detector = ChangeDetector(_shm, MAX_OBJECTS)
364520 _detector.register()
521 _detector.set_resize_callback(_on_shm_resized)
365522
366523 # Set up tile baker (ring buffer after both regions)
367524 from .shm_protocol import REGION_SIZE
368525 tile_ring_offset = REGION_SIZE * 2 # 16 MB offset
369 tile_ring_size = SHM_SIZE - tile_ring_offset # remaining 8 MB
526 tile_ring_size = TILE_RING_SIZE
370527 _baker = TileBaker(_shm, tile_size=128, max_mips=8)
371528 _baker.init_ring_buffer(tile_ring_offset, tile_ring_size)
372529
……@@ -390,6 +547,7 @@ class BRIDGE_OT_disconnect(bpy.types.Operator):
390547
391548 def execute(self, context):
392549 global _shm, _conn, _importer, _detector, _baker, _timer_running
550 global _sent_procedurals, _baked_images
393551
394552 if _detector is not None:
395553 _detector.unregister()
……@@ -397,6 +555,8 @@ class BRIDGE_OT_disconnect(bpy.types.Operator):
397555
398556 _baker = None
399557 _importer = None
558 _sent_procedurals = set()
559 _baked_images = {}
400560
401561 if _conn is not None:
402562 _conn.close()
……@@ -515,6 +675,7 @@ def register():
515675
516676def unregister():
517677 global _shm, _conn, _importer, _detector, _baker, _timer_running
678 global _sent_procedurals, _baked_images
518679
519680 # Clean up connection
520681 if _detector is not None:
……@@ -522,6 +683,8 @@ def unregister():
522683 _detector = None
523684 _baker = None
524685 _importer = None
686 _sent_procedurals = set()
687 _baked_images = {}
525688 if _conn is not None:
526689 _conn.close()
527690 _conn = None
blender_addon/blender_manifest.toml +1 -1
……@@ -2,7 +2,7 @@ schema_version = "1.0.0"
22id = "blender_godot_bridge"
33name = "Blender-Godot Bridge"
44tagline = "Real-time bidirectional scene sync with Godot"
5version = "0.14.1"
5version = "0.15.0"
66blender_version_min = "4.2.0"
77type = "add-on"
88maintainer = "blender-bridge <noreply@localhost>"
blender_addon/change_detector.py +80 -0
……@@ -66,6 +66,12 @@ class ChangeDetector:
6666 with UPDATE_TRANSFORM before Godot reads it.
6767 """
6868
69 # Minimum bytes per mesh vertex (positions + normals + UVs + overhead)
70 # 3 pos floats + 3 normal floats + 2 UV floats = 8 floats × 4 bytes = 32 bytes
71 # plus count headers (5 × 4 = 20 bytes overhead per mesh) + name + transform
72 _BYTES_PER_VERTEX = 32
73 _MESH_OVERHEAD = 256 # name + transform + headers + material + alignment
74
6975 def __init__(self, shm: ShmRegion, max_objects: int = 256):
7076 self._shm = shm
7177 self._max_objects = max_objects
……@@ -94,6 +100,10 @@ class ChangeDetector:
94100 self._dirty_images: set[str] = {}
95101 # Known texture image names (set by timer from baker._texture_ids)
96102 self._tracked_images: set[str] = set()
103 # Shading update flag: set when any material parameter changes
104 self._had_shading_update = False
105 # Callback for notifying Godot of SHM resize: fn(new_size, new_region_size)
106 self._resize_callback = None
97107
98108 def register(self):
99109 """Register the depsgraph handler."""
……@@ -162,6 +172,16 @@ class ChangeDetector:
162172 self._dirty_images = set()
163173 return dirty
164174
175 def had_shading_update(self) -> bool:
176 """Return and clear the shading update flag (set when material parameters change)."""
177 val = self._had_shading_update
178 self._had_shading_update = False
179 return val
180
181 def set_resize_callback(self, callback):
182 """Set a callback for SHM resize notifications: callback(new_size, new_region_size)."""
183 self._resize_callback = callback
184
165185 @staticmethod
166186 def _hash_transform(mat) -> int:
167187 """FNV-1a hash of 4×4 matrix floats for change detection."""
……@@ -189,6 +209,12 @@ class ChangeDetector:
189209 self._dirty_images.add(obj.name)
190210 continue
191211
212 # Detect material parameter changes (procedural texture re-bake)
213 if isinstance(obj, bpy.types.Material):
214 if update.is_updated_shading:
215 self._had_shading_update = True
216 continue
217
192218 if not isinstance(obj, bpy.types.Object):
193219 continue
194220
……@@ -310,6 +336,57 @@ class ChangeDetector:
310336 combined.update()
311337 return combined
312338
339 # -- SHM auto-resize ---------------------------------------------------
340
341 def _ensure_region_capacity(self, pending: dict):
342 """Estimate blob size for pending objects and grow SHM if needed."""
343 from .shm_protocol import HEADER_SIZE, ENTRY_SIZE, REGION_SIZE
344
345 estimated_blob = 0
346 for name, update_type in pending.items():
347 if update_type == UPDATE_DELETE:
348 estimated_blob += 64 # small entry
349 continue
350 obj = bpy.data.objects.get(name)
351 if obj is None:
352 continue
353 estimated_blob += self._MESH_OVERHEAD
354 if update_type in (UPDATE_VERTEX, UPDATE_FULL) and obj.type == "MESH":
355 mesh = obj.data
356 if mesh is not None:
357 tri_count = len(mesh.loop_triangles) if mesh.loop_triangles else len(mesh.polygons) * 2
358 vert_count = tri_count * 3
359 estimated_blob += vert_count * self._BYTES_PER_VERTEX
360
361 table_size = HEADER_SIZE + self._max_objects * ENTRY_SIZE
362 needed_region = table_size + estimated_blob
363 current_region = self._shm._region_size
364
365 if needed_region <= current_region:
366 return # fits fine
367
368 # Round up to next power-of-2 MB boundary (min 16 MB)
369 new_region = max(16 * 1024 * 1024, current_region)
370 while new_region < needed_region:
371 new_region *= 2
372
373 # Tile ring stays 8 MB
374 tile_ring_size = 8 * 1024 * 1024
375 new_shm_size = new_region * 2 + tile_ring_size
376
377 print(f"BlenderBridge: Region overflow — resizing SHM "
378 f"{self._shm.size // (1024*1024)} MB → {new_shm_size // (1024*1024)} MB "
379 f"(region {current_region // (1024*1024)} → {new_region // (1024*1024)} MB, "
380 f"estimated blob {estimated_blob // 1024} KB)")
381
382 if self._shm.resize(new_shm_size, new_region):
383 # Notify Godot to remap
384 if self._resize_callback:
385 self._resize_callback(new_shm_size, new_region)
386 print(f"BlenderBridge: SHM resize successful")
387 else:
388 print(f"BlenderBridge: SHM resize FAILED — large objects may not transfer")
389
313390 # -- Flush accumulated changes to SHM ----------------------------------
314391
315392 def flush(self) -> int:
……@@ -336,6 +413,9 @@ class ChangeDetector:
336413 for name in deleted_names:
337414 pending[name] = UPDATE_DELETE
338415
416 # Estimate blob size and resize SHM if needed
417 self._ensure_region_capacity(pending)
418
339419 self._sequence += 1
340420 # Region base for absolute mmap writes
341421 region1_base = self._shm.region_base(1)
blender_addon/shm_protocol.py +26 -0
……@@ -130,6 +130,32 @@ class ShmRegion:
130130 self.close()
131131 return False
132132
133 def resize(self, new_size: int, new_region_size: int) -> bool:
134 """Grow the shared memory and remap. Returns True on success.
135
136 Blender calls this when a mesh blob exceeds the current region.
137 The fd stays open; we ftruncate then close+reopen the mmap.
138 """
139 if self._fd < 0:
140 return False
141 try:
142 # Grow the backing file
143 os.ftruncate(self._fd, new_size)
144 # Close old mapping
145 if self._mm is not None:
146 self._mm.close()
147 # Open new mapping at new size
148 self._mm = mmap.mmap(self._fd, new_size)
149 old_size = self._size
150 self._size = new_size
151 self._region_size = new_region_size
152 # Zero-fill the newly added region (old_size..new_size)
153 self._mm[old_size:new_size] = b"\x00" * (new_size - old_size)
154 return True
155 except OSError as e:
156 print(f"BlenderBridge: SHM resize failed: {e}")
157 return False
158
133159 def close(self):
134160 """Unmap and optionally unlink the shared memory."""
135161 if self._mm is not None:
blender_bridge_addon.zip +0 -0
(no content changes — binary or rename)
src/blender_bridge.cpp +20 -2
……@@ -233,7 +233,7 @@ Error BlenderBridge::start_server(const String &socket_path) {
233233 // Set up split-region pointers
234234 _region_size = REGION_SIZE;
235235 _write_base = _shm.ptr(); // Region 0: Godot writes
236 _read_base = region_base(_shm.ptr(), 1); // Region 1: Blender writes
236 _read_base = region_base(_shm.ptr(), 1, _region_size); // Region 1: Blender writes
237237
238238 // Initialize BOTH region headers
239239 BridgeHeader hdr = {};
……@@ -264,7 +264,7 @@ Error BlenderBridge::start_server(const String &socket_path) {
264264 _known_instance_ids.clear();
265265 _next_object_id = 1;
266266
267 UtilityFunctions::print("BlenderBridge v0.14.1: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
267 UtilityFunctions::print("BlenderBridge v0.15.0: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
268268 return OK;
269269}
270270
……@@ -1243,6 +1243,24 @@ void BlenderBridge::_handle_uds_message(const UdsMessage &msg) {
12431243 return;
12441244 }
12451245
1246 if (text.substr(0, 12) == "shm_resized:") {
1247 // Blender grew the SHM: shm_resized:new_size,new_region_size
1248 size_t new_size = 0, new_region_size = 0;
1249 if (sscanf(text.c_str() + 12, "%zu,%zu", &new_size, &new_region_size) == 2) {
1250 if (_shm.resize(new_size)) {
1251 _shm_size = (int)new_size;
1252 _region_size = new_region_size;
1253 _write_base = _shm.ptr();
1254 _read_base = region_base(_shm.ptr(), 1, _region_size);
1255 UtilityFunctions::print("BlenderBridge: SHM resized to ", (int)new_size,
1256 " bytes (region=", (int)new_region_size, ")");
1257 } else {
1258 UtilityFunctions::printerr("BlenderBridge: SHM resize failed!");
1259 }
1260 }
1261 return;
1262 }
1263
12461264 if (text == "finalize") {
12471265 // Blender requests finalization — save textures + scene
12481266 Object *root_obj = ObjectDB::get_instance(_export_root_id);
src/shm_protocol.h +23 -3
……@@ -100,10 +100,10 @@ inline size_t data_blob_offset(uint32_t max_objects) {
100100// Tile ring [2*REGION_SIZE, SHM_SIZE): Texture streaming
101101// Each region has its own header, object table, and data blob area.
102102
103constexpr size_t REGION_SIZE = 8 * 1024 * 1024; // 8 MB per region
103constexpr size_t REGION_SIZE = 8 * 1024 * 1024; // 8 MB default per region
104104
105inline void *region_base(void *shm, int region) {
106 return static_cast<uint8_t *>(shm) + region * REGION_SIZE;
105inline void *region_base(void *shm, int region, size_t region_size = REGION_SIZE) {
106 return static_cast<uint8_t *>(shm) + region * region_size;
107107}
108108
109109// ── POSIX shared memory wrapper ────────────────────────────────────────────
……@@ -160,6 +160,26 @@ public:
160160 return true;
161161 }
162162
163 // Grow the shared memory and remap (called when Blender sends shm_resized)
164 bool resize(size_t new_size) {
165 if (_fd < 0 || !_ptr)
166 return false;
167 // Grow the backing file
168 if (ftruncate(_fd, new_size) < 0)
169 return false;
170 // Unmap old
171 munmap(_ptr, _size);
172 // Remap at new size
173 _ptr = mmap(nullptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
174 if (_ptr == MAP_FAILED) {
175 _ptr = nullptr;
176 _size = 0;
177 return false;
178 }
179 _size = new_size;
180 return true;
181 }
182
163183 void destroy() {
164184 if (_ptr) {
165185 munmap(_ptr, _size);