Skip to content

Commit bc820a0

Browse files
committed
Clarify memory management policy and ownership rules in README
1 parent a5ad950 commit bc820a0

1 file changed

Lines changed: 82 additions & 24 deletions

File tree

README.md

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -542,41 +542,99 @@ Note: Image display is a feature introduced experimentally in a fork of libui-ng
542542

543543
## Memory Management Policy
544544

545-
libui-ng is a C library, so normally the user is expected to manage memory manually.
546-
However, it also provides some automatic memory management. The basic rule is: when a parent control is destroyed, all of its child controls are automatically destroyed as well.
545+
UIng wraps libui-ng, a C library with explicit ownership rules. Crystal's GC keeps wrapper objects alive, but it does not decide when native widgets are destroyed. Use `destroy` for controls and `free` for standalone native resources.
547546

548-
For example, when destroy is called on controls such as Window, Box, Grid, Group, or Tab, it first destroys all child controls and then destroys itself.
547+
The main rule is simple: a parent control owns its children. Destroying a parent destroys the native children too, and UIng marks the child wrappers as destroyed. Do not call `destroy` on a child while it still belongs to a parent.
549548

550-
In the case of a Window, there are two common situations:
549+
```crystal
550+
window = UIng::Window.new("App", 400, 300)
551+
box = UIng::Box.new(:vertical)
552+
button = UIng::Button.new("OK")
553+
554+
box.append(button)
555+
window.child = box
556+
557+
window.destroy # also destroys box and button
558+
```
559+
560+
If you want to reuse a child, detach it first when the container supports it:
561+
562+
```crystal
563+
box.delete(button) # detaches; button is still alive
564+
other_box.append(button)
565+
```
551566

552-
When the user clicks the close (×) button on the title bar
553-
In this case, `on_closing` is triggered. If the callback returns `true`, libui-ng destroys the native window. UIng mirrors that native destruction by marking the `Window` wrapper and all child wrappers as destroyed. Do not call `Window#destroy` from inside `on_closing`; return `true` to allow the close, or `false` to keep the window open.
567+
Container behavior:
554568

555-
When on_should_quit is triggered
556-
This means the entire program is about to quit. But here, Window#destroy is not called automatically, so the user needs to call it explicitly.
569+
- `Window` and `Group` have one child. Assigning a new child detaches the old child without destroying it. Destroying the `Window` or `Group` destroys its current child.
570+
- `Box`, `Form`, and `Tab` support `delete(index)` and `delete(child)`. `delete` detaches the child; call `child.destroy` afterwards if you are done with it.
571+
- `Grid` does not currently expose a delete API. Destroy the whole grid, or build a new grid if you need to remove children.
572+
- Controls with no parent can be destroyed directly with `destroy`.
557573

558-
Crystal has garbage collection (GC) and provides a finalize hook when objects are freed. However, GC timing is non-deterministic, and libui-ng controls often require deterministic destruction order.
574+
Window closing has one special rule. In `Window#on_closing`, return `true` to let libui-ng destroy the native window, or `false` to keep it open. Do not call `window.destroy` inside that callback.
575+
576+
```crystal
577+
window.on_closing do
578+
UIng.quit
579+
true
580+
end
581+
```
559582

560-
Therefore, UIng's primary policy is:
583+
`on_should_quit` does not destroy windows for you. If your app exits from there, explicitly destroy any windows you created.
561584

562-
- Control objects (`Window`, `Box`, `Grid`, `Tab`, etc.) are managed with explicit `destroy`.
563-
- Child controls are owned by their parent. To remove a child without destroying it, use the container's delete/remove API where available. Destroying a child directly while it still has a parent is not allowed.
564-
- C resources that require strict ordering (for example `Table::Model`) are managed with explicit `free`.
565-
- `finalize` is never treated as a primary cleanup mechanism for order-sensitive resources.
585+
Some non-control resources must be freed manually:
566586

567-
Some lightweight wrappers include idempotent cleanup in `finalize` as a fallback, but this is best-effort only and not a correctness guarantee.
587+
- `Table::Model`: destroy all `Table` controls using the model first, then call `model.free`.
588+
- `Image`: call `image.free` when it is no longer needed. `ImageView#image=` copies or retains what it needs, but table image values borrow the image, so keep the `Image` alive while the table may display it.
589+
- `Table::Selection`: selections passed to `on_selection_changed` are freed automatically after the callback. If you call `table.selection` manually, free the returned selection when done.
568590

569-
It may seem a bit tricky, but understanding how libui-ng handles memory will help you avoid problems when building applications.
591+
After `destroy` or `free`, do not use the wrapper again.
570592

571-
Some controls require specific destruction order for proper memory cleanup
593+
Example cleanup patterns:
572594

573-
- [#6](https://github.com/kojix2/uing/issues/6)
574-
- Table
575-
- [#19](https://github.com/kojix2/uing/issues/19)
576-
- MultilineEntry
577-
- Tab
578-
- Grid
579-
- Area
595+
```crystal
596+
# Reuse a child by detaching it first.
597+
left = UIng::Box.new(:vertical)
598+
right = UIng::Box.new(:vertical)
599+
button = UIng::Button.new("Move me")
600+
601+
left.append(button)
602+
left.delete(button) # button is detached, not destroyed
603+
right.append(button)
604+
```
605+
606+
```crystal
607+
# Remove a child permanently.
608+
box = UIng::Box.new(:vertical)
609+
button = UIng::Button.new("Delete me")
610+
611+
box.append(button)
612+
box.delete(button)
613+
button.destroy
614+
```
615+
616+
```crystal
617+
# Table models must outlive tables that use them.
618+
handler = UIng::Table::Model::Handler.new
619+
model = UIng::Table::Model.new(handler)
620+
table = UIng::Table.new(model)
621+
622+
# ... use table ...
623+
624+
table.destroy
625+
model.free
626+
```
627+
628+
```crystal
629+
# Images can be freed after ImageView receives them.
630+
image = UIng::Image.new(16, 16)
631+
pixels = Bytes.new(16 * 16 * 4, 0_u8)
632+
image.append(pixels.to_unsafe.as(Pointer(Void)), 16, 16, 16 * 4)
633+
634+
image_view = UIng::ImageView.new
635+
image_view.image = image
636+
image.free
637+
```
580638

581639
## Limitations
582640

0 commit comments

Comments
 (0)