Skip to content

Move sdl concext to backend#24

Merged
kimkulling merged 3 commits intomainfrom
refactoring/move_sdl_ctx_to_backend
Mar 17, 2026
Merged

Move sdl concext to backend#24
kimkulling merged 3 commits intomainfrom
refactoring/move_sdl_ctx_to_backend

Conversation

@kimkulling
Copy link
Copy Markdown
Owner

@kimkulling kimkulling commented Mar 17, 2026

Summary by CodeRabbit

  • Refactor

    • Internal resource and lifecycle handling for the SDL renderer was reworked to improve initialization and teardown robustness while preserving existing APIs.
  • Bug Fixes

    • Added defensive null checks and safer destruction paths around window/renderer resources to reduce crashes and improve stability and error reporting.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a1ffaf7a-b448-4c94-81b3-86326fe58c44

📥 Commits

Reviewing files that changed from the base of the PR and between 26fd265 and 2efe90e.

📒 Files selected for processing (1)
  • src/backends/sdl2_renderer.cpp

Walkthrough

The PR changes SDL context handling from an in-struct value to a heap-allocated pointer (SDLContext*). The SDLContext definition and lifecycle (create/destroy) move to the SDL backend header; all uses are updated from dot (.) to arrow (->) access and guarded with null checks.

Changes

Cohort / File(s) Summary
Core Context
src/tinyui.h
Removed in-file SDLContext definition, added forward declaration, and changed Context::mSDLContext from a value to SDLContext *mSDLContext{ nullptr };.
SDL Backend Interface
src/backends/sdl2_renderer.h
Added struct SDLContext (members: mWindow, mSurface, mRenderer, mOwner) with static create() and destroy() lifecycle methods.
SDL Backend Implementation
src/backends/sdl2_renderer.cpp
Replaced dot-accesses with pointer-accesses for ctx.mSDLContext across creation, rendering, and teardown; introduced SDLContext::create() usage, added nullptr guards, and ensured conditional destruction and nulling of pointers.
Core Usage Sites
src/tinyui.cpp, src/widgets.cpp
Updated surface/renderer access and null-checks to use ctx.mSDLContext->... in getSurfaceInfo(), release(), and widget rendering functions.

Sequence Diagram(s)

sequenceDiagram
    participant App as App (tinyui)
    participant Ctx as SDLContext*
    participant SDL as SDL (window/renderer)

    App->>Ctx: ctx.mSDLContext = SDLContext::create()
    alt creation success
        Ctx->>SDL: create window / create renderer / get surface
        SDL-->>Ctx: window/renderer/surface handles
        App->>SDL: render commands (via Ctx->mRenderer, Ctx->mSurface)
        SDL-->>App: present frame
    end
    App->>Ctx: Ctx->destroy()
    Ctx->>SDL: destroy renderer / destroy window (if mOwner)
    SDL-->>Ctx: resources freed
    Ctx-->>App: delete self, set pointer null
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Move sdl concext to backend #24 — Converts SDLContext from a value to a pointer and adds backend SDLContext create/destroy lifecycle (same code-level change).
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title contains a typo ('concext' instead of 'context') and is vague about what 'moving SDL context to backend' entails, making it unclear without reading the PR details. Correct the typo to 'context' and clarify the intent, such as 'Refactor SDLContext lifecycle management into backend module' or 'Move SDLContext to sdl2_renderer backend'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactoring/move_sdl_ctx_to_backend
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/backends/sdl2_renderer.h (1)

83-85: create() + delete this is a fragile ownership model for SDLContext.

This API is easy to misuse and is already triggering static analysis failures. Prefer RAII ownership (std::unique_ptr) to remove manual new/delete this lifecycle hazards.

Also applies to: 88-100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backends/sdl2_renderer.h` around lines 83 - 85, The factory function
SDLContext::create currently returns a raw pointer and relies on callers calling
"delete this", which is fragile; change SDLContext::create to return
std::unique_ptr<SDLContext> (use std::make_unique<SDLContext>), update the class
signature to include <memory>, and remove any internal "delete this" calls so
destruction is handled by unique_ptr; update all call sites that expect
SDLContext* (constructors/factory uses and any ownership transfers) to accept
and forward std::unique_ptr<SDLContext> (or call get() only where non-owning raw
pointer is needed) and ensure no code calls delete on the object directly. Also
search for and remove or refactor any methods that currently call "delete this"
to instead let the unique_ptr control lifetime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/backends/sdl2_renderer.cpp`:
- Line 247: Replace the C-style NULL with C++ nullptr in the SDL_RenderCopy
call: update the call to SDL_RenderCopy(ctx.mSDLContext->mRenderer,
messageTexture, nullptr, &Message_rect) so it uses nullptr for the source
rectangle parameter (refer to SDL_RenderCopy, ctx.mSDLContext->mRenderer,
messageTexture, and Message_rect).
- Around line 183-184: Both releaseRenderer() and releaseScreen() currently
dereference ctx.mSDLContext without validating it; after the existing
!ctx.mCreated check, add a null check for ctx.mSDLContext and return
InvalidHandle when it's null to avoid crashes. Locate the releaseRenderer and
releaseScreen functions and guard uses of ctx.mSDLContext (and its members like
mRenderer) with an if (!ctx.mSDLContext) return InvalidHandle; so subsequent
calls (e.g., SDL_DestroyRenderer, SDL_DestroyWindow) only run when the SDL
context is valid. Ensure you set ctx.mSDLContext pointers (like
mRenderer/mWindow) to nullptr after destruction as already done.
- Around line 259-263: The initScreen function currently calls
SDLContext::create() before checking whether ctx.mSDLContext already exists,
making the "already created" guard ineffective; change the logic so you first
check if ctx.mSDLContext != nullptr and return the error if so, then call
SDLContext::create() to allocate. In the second initScreen overload, add a null
check (ensure ctx.mSDLContext != nullptr) before dereferencing any members (the
block that accesses mWindow, mRenderer, mTexture), and return an error if it's
null. Replace any use of NULL with nullptr (the earlier occurrence around the
context init). Finally, add null checks at the start of releaseScreen and
beginRender to safely return if ctx.mSDLContext is nullptr before dereferencing
its members.
- Around line 328-330: The external-window overload of initScreen currently
dereferences ctx.mSDLContext->mRenderer and ->mWindow without ensuring
ctx.mSDLContext is valid; modify that initScreen variant to check if
ctx.mSDLContext is null and if so call SDLContext::create() (the same
initialization path used by the coordinate-based initScreen), then assign
mRenderer and mWindow; ensure you update references to ctx.mSDLContext,
mRenderer and mWindow only after confirming/creating ctx.mSDLContext to avoid
null-pointer dereference.

In `@src/tinyui.cpp`:
- Around line 174-175: The code currently only nulls ctx.mSDLContext's member
pointers instead of freeing the heap-allocated SDLContext object; call
SDLContext::destroy() on ctx.mSDLContext to let it clean up SDL resources and
delete itself (or delete ctx.mSDLContext if destroy() is a static/member that
returns void) and then set ctx.mSDLContext = nullptr; locate the place where
ctx.mSDLContext is reset (the lines setting mRenderer/mWindow to nullptr) and
replace that with a call to SDLContext::destroy(ctx.mSDLContext) or
ctx.mSDLContext->destroy(), followed by ctx.mSDLContext = nullptr to avoid
leaks.

In `@src/widgets.cpp`:
- Line 224: The renderer guard dereferences ctx.mSDLContext directly (checking
ctx.mSDLContext->mRenderer) which can segfault when mSDLContext is nullptr;
update each widget builder to first check if ctx.mSDLContext is nullptr and
return InvalidRenderHandle before accessing mSDLContext->mRenderer (apply the
same pattern for all locations that check mSDLContext->mRenderer, e.g., the
guard in the widget creation functions in src/widgets.cpp that currently read if
(ctx.mSDLContext->mRenderer == nullptr) — add an early if (!ctx.mSDLContext)
return InvalidRenderHandle; above those checks so mSDLContext is validated
before dereference).

---

Nitpick comments:
In `@src/backends/sdl2_renderer.h`:
- Around line 83-85: The factory function SDLContext::create currently returns a
raw pointer and relies on callers calling "delete this", which is fragile;
change SDLContext::create to return std::unique_ptr<SDLContext> (use
std::make_unique<SDLContext>), update the class signature to include <memory>,
and remove any internal "delete this" calls so destruction is handled by
unique_ptr; update all call sites that expect SDLContext* (constructors/factory
uses and any ownership transfers) to accept and forward
std::unique_ptr<SDLContext> (or call get() only where non-owning raw pointer is
needed) and ensure no code calls delete on the object directly. Also search for
and remove or refactor any methods that currently call "delete this" to instead
let the unique_ptr control lifetime.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d489285-a264-4349-b895-23bf59f61122

📥 Commits

Reviewing files that changed from the base of the PR and between 26b49ab and a755993.

📒 Files selected for processing (5)
  • src/backends/sdl2_renderer.cpp
  • src/backends/sdl2_renderer.h
  • src/tinyui.cpp
  • src/tinyui.h
  • src/widgets.cpp

Comment on lines +328 to 330
ctx.mSDLContext->mRenderer = renderer;
ctx.mSDLContext->mWindow = window;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "sdl2_renderer.cpp" -o -name "sdl2_renderer.h" 2>/dev/null | head -20

Repository: kimkulling/tiny_ui

Length of output: 44


🏁 Script executed:

find . -name "sdl2_renderer.cpp" -o -name "sdl2_renderer.h" 2>/dev/null

Repository: kimkulling/tiny_ui

Length of output: 125


🏁 Script executed:

find . -type f -name "*sdl2*" 2>/dev/null | head -20

Repository: kimkulling/tiny_ui

Length of output: 189


🏁 Script executed:

git ls-files | grep -i sdl2

Repository: kimkulling/tiny_ui

Length of output: 181


🏁 Script executed:

git ls-files | grep -E "\.(cpp|h|hpp)$" | head -20

Repository: kimkulling/tiny_ui

Length of output: 306


🏁 Script executed:

git ls-files | grep -E "src/backends" | head -20

Repository: kimkulling/tiny_ui

Length of output: 181


🏁 Script executed:

ls -la src/backends/ 2>/dev/null || echo "Directory not found"

Repository: kimkulling/tiny_ui

Length of output: 443


🏁 Script executed:

git ls-files | head -30

Repository: kimkulling/tiny_ui

Length of output: 566


🏁 Script executed:

rg -l "initScreen" --type cpp --type h

Repository: kimkulling/tiny_ui

Length of output: 166


🏁 Script executed:

rg "initScreen" -A 5 -B 2 --type cpp --type h | head -100

Repository: kimkulling/tiny_ui

Length of output: 3559


🏁 Script executed:

test -f "src/backends/sdl2_renderer.cpp" && echo "File exists" || echo "File does not exist"

Repository: kimkulling/tiny_ui

Length of output: 73


🏁 Script executed:

find . -type f \( -name "*.cpp" -o -name "*.h" \) 2>/dev/null | sort

Repository: kimkulling/tiny_ui

Length of output: 328


🏁 Script executed:

ls -la

Repository: kimkulling/tiny_ui

Length of output: 1118


🏁 Script executed:

pwd && ls -la src/ 2>/dev/null | head -20

Repository: kimkulling/tiny_ui

Length of output: 561


🏁 Script executed:

find . -path "*backends*" -name "*.cpp" -o -path "*backends*" -name "*.h" | head -20

Repository: kimkulling/tiny_ui

Length of output: 189


🏁 Script executed:

find . -name "*sdl2_renderer*" -type f

Repository: kimkulling/tiny_ui

Length of output: 125


🏁 Script executed:

rg "initScreen" -C 20 | head -150

Repository: kimkulling/tiny_ui

Length of output: 7254


🏁 Script executed:

rg "mSDLContext->mRenderer" -B 5 -A 2

Repository: kimkulling/tiny_ui

Length of output: 10343


🏁 Script executed:

cat "src/backends/sdl2_renderer.cpp" 2>/dev/null | head -50

Repository: kimkulling/tiny_ui

Length of output: 1869


🏁 Script executed:

ls -la src/ 2>/dev/null || ls -la

Repository: kimkulling/tiny_ui

Length of output: 542


🏁 Script executed:

git ls-files --cached 2>/dev/null | head -30

Repository: kimkulling/tiny_ui

Length of output: 566


🏁 Script executed:

git status 2>&1 | head -10
git ls-files 2>/dev/null | wc -l

Repository: kimkulling/tiny_ui

Length of output: 265


🏁 Script executed:

find . -type f -name "*.cpp" 2>/dev/null | head -20

Repository: kimkulling/tiny_ui

Length of output: 181


🏁 Script executed:

ls -la && find . -maxdepth 3 -type f \( -name "*.cpp" -o -name "*.h" \) 2>/dev/null | head -30

Repository: kimkulling/tiny_ui

Length of output: 1385


🌐 Web query:

SDL2 initScreen null pointer safety pattern C++

💡 Result:

A solid “null-pointer safe” initScreen() pattern for SDL2 in C++ is:

  1. Check every SDL call that can fail (returns nullptr/NULL or <0) and bail immediately, logging SDL_GetError() (e.g., SDL_CreateWindow returns NULL on failure and you should call SDL_GetError() for details). [1]
  2. Use RAII so partially-initialized resources don’t leak if you early-return (wrap SDL_Window*, SDL_Renderer*, etc. in std::unique_ptr with SDL destroy functions as custom deleters). [2][3]
`#include` <SDL.h>
`#include` <memory>
`#include` <stdexcept>
`#include` <string>

struct SdlError : std::runtime_error {
  using std::runtime_error::runtime_error;
};

using Window   = std::unique_ptr<SDL_Window,   decltype(&SDL_DestroyWindow)>;
using Renderer = std::unique_ptr<SDL_Renderer, decltype(&SDL_DestroyRenderer)>;

struct Screen {
  Window   window   {nullptr, SDL_DestroyWindow};
  Renderer renderer {nullptr, SDL_DestroyRenderer};
};

inline void sdl_ok(int rc, const char* what) {
  if (rc < 0) throw SdlError(std::string(what) + ": " + SDL_GetError());
}

inline Screen initScreen(const char* title, int w, int h) {
  sdl_ok(SDL_Init(SDL_INIT_VIDEO), "SDL_Init");

  Screen s;

  s.window.reset(SDL_CreateWindow(title,
                                 SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                 w, h, SDL_WINDOW_SHOWN));
  if (!s.window) throw SdlError(std::string("SDL_CreateWindow: ") + SDL_GetError());

  s.renderer.reset(SDL_CreateRenderer(s.window.get(), -1,
                                      SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC));
  if (!s.renderer) throw SdlError(std::string("SDL_CreateRenderer: ") + SDL_GetError());

  return s; // move/NRVO
}

// Usage:
// int main() {
//   try {
//     auto screen = initScreen("My Game", 1280, 720);
//     ...
//   } catch (const std::exception& e) {
//     SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s\n", e.what());
//   }
//   SDL_Quit(); // or wrap SDL_Init/SDL_Quit in its own RAII guard
// }

Why this is “null-safe”: you never dereference window/renderer unless the unique_ptr is non-null, and failures immediately stop initialization (with an error message from SDL_GetError()). [1][2]

Sources: SDL “returns NULL on failure; call SDL_GetError()” guidance [1]; RAII with unique_ptr + SDL destroy-function deleters [2][3].

[1] SDL Wiki: SDL_CreateWindow return value / SDL_GetError() on failure (wiki.libsdl.org)
[2] RAII + unique_ptr custom deleter technique (SDL example) (lethalguitar.wordpress.com)
[3] SDL resource wrappers using unique_ptr custom deleters (swarminglogic.com)

Citations:


🏁 Script executed:

rg "mSDLContext" -B 10 -A 5 --type cpp

Repository: kimkulling/tiny_ui

Length of output: 23230


🏁 Script executed:

rg "ctx.mSDLContext->mRenderer" -B 20 -A 5

Repository: kimkulling/tiny_ui

Length of output: 26191


🏁 Script executed:

rg "ret_code initScreen" -A 30 --type cpp

Repository: kimkulling/tiny_ui

Length of output: 3344


🏁 Script executed:

rg "initScreen\s*\(" -B 2 -A 15 --type cpp | head -200

Repository: kimkulling/tiny_ui

Length of output: 7173


🏁 Script executed:

ast-grep --pattern 'fn initScreen($$$) { $$$ }' 2>/dev/null || rg "void initScreen|ret_code initScreen" -A 20

Repository: kimkulling/tiny_ui

Length of output: 2836


🏁 Script executed:

rg "SDLContext" --type cpp --type h | head -40

Repository: kimkulling/tiny_ui

Length of output: 3201


🏁 Script executed:

ls -la src/backends/

Repository: kimkulling/tiny_ui

Length of output: 443


External-window initScreen dereferences mSDLContext without null check.

At lines 328-330, the function directly dereferences ctx.mSDLContext->mRenderer and ctx.mSDLContext->mWindow without first ensuring mSDLContext is initialized. Unlike the coordinate-based initScreen overload which calls SDLContext::create(), this external-window variant assumes the pointer already exists. If called without prior initialization, this causes a null pointer dereference. Validate that mSDLContext is not null before dereferencing, or initialize it if needed.

Proposed fix
-    ctx.mSDLContext->mRenderer = renderer;
-    ctx.mSDLContext->mWindow = window;
+    if (ctx.mSDLContext == nullptr) {
+        ctx.mSDLContext = SDLContext::create();
+    }
+    if (ctx.mSDLContext == nullptr) {
+        return ErrorCode;
+    }
+    ctx.mSDLContext->mRenderer = renderer;
+    ctx.mSDLContext->mWindow = window;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ctx.mSDLContext->mRenderer = renderer;
ctx.mSDLContext->mWindow = window;
if (ctx.mSDLContext == nullptr) {
ctx.mSDLContext = SDLContext::create();
}
if (ctx.mSDLContext == nullptr) {
return ErrorCode;
}
ctx.mSDLContext->mRenderer = renderer;
ctx.mSDLContext->mWindow = window;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backends/sdl2_renderer.cpp` around lines 328 - 330, The external-window
overload of initScreen currently dereferences ctx.mSDLContext->mRenderer and
->mWindow without ensuring ctx.mSDLContext is valid; modify that initScreen
variant to check if ctx.mSDLContext is null and if so call SDLContext::create()
(the same initialization path used by the coordinate-based initScreen), then
assign mRenderer and mWindow; ensure you update references to ctx.mSDLContext,
mRenderer and mWindow only after confirming/creating ctx.mSDLContext to avoid
null-pointer dereference.

@kimkulling kimkulling merged commit cc9fc7f into main Mar 17, 2026
1 check passed
@sonarqubecloud
Copy link
Copy Markdown

@kimkulling kimkulling deleted the refactoring/move_sdl_ctx_to_backend branch March 17, 2026 23:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants