Error handling in this project is part of the runtime architecture, not a
separate afterthought. Startup, shutdown, and runtime exits all depend on the
same ownership rules inside t_app.
Failure paths
Startup can fail anywhere, cleanup must still be safe
This codebase relies on progressive initialization and centralized cleanup. Every failure
path either returns immediately before resources exist or routes through `free_app(&app)` once
ownership has started.
-
1
init_app(&app) NULL base
zero-init and NULL-init the whole app state so cleanup always has safe defaults
return 1
-
2
bonus_levels_init(&app) bonus
prepare optional level context before parsing starts
free_app(&app) + return 1
-
3
parse_file(&app, argv[1]) parsing
load config, validate map, and stop startup on parser/validator errors
free_app(&app) + return 1
-
4
init_player_vectors(&app)
convert validated spawn orientation into direction and camera plane
free_app(&app) + return 1
-
5
init_mlx(&app) MLX alloc
allocate display, window, textures, and frame buffer resources
free_app(&app) + return 1
-
6
mlx_loop(app.mlx_ptr) runtime
enter runtime loop after startup has succeeded
runtime exit paths call free_app(...)
`ESC` / window cross `close_window(app)` → `free_app(app)` → `exit(0)`
fatal resize/init error `error_put(...)` → `free_app(app)` → `exit(1)`
free config texture paths if ptr
destroy mandatory textures if img_ptr
run bonus shutdown helpers bonus
destroy frame image if img_ptr
destroy window if win_ptr
destroy display + free mlx_ptr if mlx_ptr
free map grid if grid
click to replay a failure at init_mlx no separate init flag table safe partial cleanup single central shutdown path normal and error exits aligned
The error model is designed to guarantee:
- explicit error reporting through
Error\n...
- predictable cleanup on every failure path
- no double-destroy on MLX resources
- no leaks from partially initialized state
- safe termination on both normal exit and fatal exit
Resources are initialized progressively, and cleanup relies on pointer validity
instead of a separate init-state table.
That means:
init_app starts from a safe zeroed state
- each later stage may allocate more resources
- any failure after ownership begins routes through
free_app(&app)
- each cleanup action checks whether the owned pointer is valid before destroy/free
The main runtime exits are:
ESC
- window close event
- fatal resize/init path that reports an error before exiting
Normal close uses close_window(...), which then calls free_app(...) before
exit(0). Error exits use the same ownership model but end with an error status.
free_app(...) is the central cleanup gate for:
- config strings
- map grid
- frame image
- mandatory textures
- bonus subsystem shutdown
- MLX window / display resources
Because it is pointer-guarded, it naturally supports partial initialization.
This model explicitly covers:
- failure before MLX initialization
- failure during MLX setup
- parser failure after dynamic allocations
- partial image or texture initialization
- repeated cleanup entry points routed to the same function
This architecture is easy to defend because it is easy to explain:
- failure paths are visible in control flow
- ownership is centralized in
t_app
- cleanup logic is deterministic
- no separate hidden state machine is needed to know what may be freed
srcs/tools/utils.c
srcs/core/main.c
srcs/core/shutdown.c
srcs_bonus/doors/
srcs_bonus/hud/
srcs_bonus/pickups/
srcs_bonus/sprites/