Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a header which uses C++ lifetimes to guard part of the api? #6963

Closed
Andersama opened this issue Oct 27, 2023 · 4 comments
Closed

Comments

@Andersama
Copy link

My Issue/Question:
A lot of the api uses begin / end paired functions, in fact a lot of these functions do additional asserts to make sure that to some degree that these stay in some amount of sync.

ImGui::Begin("Example Code");
...etc...
ImGui::End();

I'm curious if there's a header or project which might've wrapped these function calls to provide a struct which calls the appropriate destructor. Think the trick used to defer a lambda function to the end of a function call.

#include <iostream>
// Note: this isn't a great example, just a rough mockup
template<typename Callback>
struct defer {
    Callback cb;
    defer(Callback func) {
       cb = func;
    }
    ~defer(){
       cb();
    }
}

int some_func() {
   defer later{[](){
       //called after "first" because structs would get cleaned up right before the function returns
       std::cout << "last\n";
   }};
   std::cout << "first\n";
}

So for example the api might look like:

ImGuiBeginGaurd begin_result = ImGuiGaurd::Begin("Example Window");
//
//behind the scene we can now be sure that ~ImGuiBeginGaurd() would get called and
//presumably the correct "End" call

The only slight concern I have if this doesn't exist already would be the parts of the api which don't require a paired "End" call which may make things confusing.

@emoon
Copy link
Contributor

emoon commented Oct 27, 2023

See #2096 and #2197

@Andersama
Copy link
Author

Andersama commented Oct 27, 2023

Much appreciated, thank you.

Edit: In the process of waiting for answer I decided to write my own, not sure this is complete for all of imgui's apis, but it seems complete for "imgui.h" (There may be bugs in here).

#pragma once
#include "imgui.h"

namespace ImGuiGaurd {
	struct DeferEnd {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEnd() {
			ImGui::End();
		}
	};

	[[nodiscard]] DeferEnd Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0) {
		return DeferEnd{ ImGui::Begin(name, p_open, flags) };
	};

	struct DeferEndChild {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndChild() {
			ImGui::End();
		}
	};

	[[nodiscard]] DeferEndChild BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0) {
		return DeferEndChild{ ImGui::BeginChild(str_id, size, border, flags) };
	};

	[[nodiscard]] DeferEndChild BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0) {
		return DeferEndChild{ ImGui::BeginChild(id, size, border, flags) };
	};

	struct DeferEndGroup {
		~DeferEndGroup() {
			ImGui::EndGroup();
		}
	};

	[[nodiscard]] DeferEndGroup BeginGroup() {
		ImGui::BeginGroup();
		return DeferEndGroup{};
	};

	struct DeferEndCombo {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndCombo() {
			if (result)
				ImGui::EndCombo();
		}
	};

	DeferEndCombo BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0) {
		return DeferEndCombo{ ImGui::BeginCombo(label, preview_value, flags) };
	};

	DeferEndCombo Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1) {
		return DeferEndCombo{ ImGui::Combo(label, current_item, items, items_count, popup_max_height_in_items) };
	};

	DeferEndCombo Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1) {
		return DeferEndCombo{ ImGui::Combo(label, current_item, items_separated_by_zeros, popup_max_height_in_items) };
	};

	DeferEndCombo Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1) {
		return DeferEndCombo{ ImGui::Combo(label, current_item, items_getter, data, items_count, popup_max_height_in_items) };
	};

	struct DeferEndListBox {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndListBox() {
			if (result)
				ImGui::EndListBox();
		}
	};

	DeferEndListBox BeginListBox(const char* label, const ImVec2& size = ImVec2(0, 0)) {
		return DeferEndListBox{ ImGui::BeginListBox(label, size) };
	};

	DeferEndListBox ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1) {
		return DeferEndListBox{ ImGui::ListBox(label, current_item, items, items_count, height_in_items) };
	};

	DeferEndListBox ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1) {
		return DeferEndListBox{ ImGui::ListBox(label, current_item, items_getter, data, items_count, height_in_items) };
	};

	struct DeferEndMenuBar {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndMenuBar() {
			if (result)
				ImGui::EndMenuBar();
		}
	};

	DeferEndMenuBar BeginMenuBar() {
		return DeferEndMenuBar{ ImGui::BeginMenuBar() };
	};

	struct DeferEndMainMenuBar {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndMainMenuBar() {
			if (result)
				ImGui::EndMainMenuBar();
		}
	};

	DeferEndMainMenuBar BeginMainMenuBar() {
		return DeferEndMainMenuBar{ ImGui::BeginMainMenuBar() };
	};

	struct DeferEndMenu {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndMenu() {
			if (result)
				ImGui::EndMenu();
		}
	};

	DeferEndMenu BeginMenu(const char* label, bool enabled = true) {
		return DeferEndMenu{ ImGui::BeginMenu(label, enabled) };
	};

	struct DeferEndTooltip {
		~DeferEndTooltip() {
			ImGui::EndTooltip();
		}
	};

	DeferEndTooltip BeginTooltip() {
		ImGui::BeginTooltip();
		return DeferEndTooltip{};
	};

	struct DeferEndPopup {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndPopup() {
			if (result)
				ImGui::EndPopup();
		}
	};

	DeferEndPopup BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0) {
		return DeferEndPopup{ ImGui::BeginPopup(str_id, flags) };
	};

	DeferEndPopup BeginPopupModal(const char* str_id, bool* p_open, ImGuiWindowFlags flags = 0) {
		return DeferEndPopup{ ImGui::BeginPopupModal(str_id, p_open, flags) };
	};

	struct DeferEndTable {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndTable() {
			if (result)
				ImGui::EndTable();
		}
	};

	DeferEndTable BeginTable(const char* str_id, int column, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f) {
		return DeferEndTable{ ImGui::BeginTable(str_id, column, flags, outer_size, inner_width) };
	};

	struct DeferEndTabBar {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndTabBar() {
			if (result)
				ImGui::EndTabBar();
		}
	};

	DeferEndTabBar BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0) {
		return DeferEndTabBar{ ImGui::BeginTabBar(str_id, flags) };
	};

	struct DeferEndTabItem {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndTabItem() {
			if (result)
				ImGui::EndTabItem();
		}
	};

	DeferEndTabItem BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0) {
		return DeferEndTabItem{ ImGui::BeginTabItem(label, p_open, flags) };
	};

	struct DeferEndDragDropSource {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndDragDropSource() {
			if (result)
				ImGui::EndDragDropSource();
		}
	};

	DeferEndDragDropSource BeginDragDropSource(ImGuiDragDropFlags flags = 0) {
		return DeferEndDragDropSource{ ImGui::BeginDragDropSource(flags) };
	};

	struct DeferEndDragDropTarget {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndDragDropTarget() {
			if (result)
				ImGui::EndDragDropTarget();
		}
	};

	DeferEndDragDropTarget BeginDragDropTarget() {
		return DeferEndDragDropTarget{ ImGui::BeginDragDropTarget() };
	};

	struct DeferEndDisabled {
		~DeferEndDisabled() {
			ImGui::EndDisabled();
		}
	};

	DeferEndDisabled BeginDisabled(bool disabled = true) {
		return DeferEndDisabled{};
	};

	struct DeferEndChildFrame {
		bool result;

		operator bool() {
			return result;
		}

		~DeferEndChildFrame() {
			ImGui::EndChildFrame();
		}
	};

	DeferEndChildFrame BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0) {
		return DeferEndChildFrame{ ImGui::BeginChildFrame(id, size, flags) };
	};
}

@ocornut
Copy link
Owner

ocornut commented Oct 27, 2023

Combo() and ListBox() are not BeginXXX functions so don’t need this.

Traditionally my reluctance with this that Push/Pop functions may be interleaved in asymmetrical manners with other functions, and that adopting this style tends to prevents it.
Eg:
PushStyleVar() / Begin() / PopStyleVar() / … / End()
Is a valid and very useful pattern.

Your idiom may only be safely used if the user opens a scope for every begin/end, not doing so may lead to undesired behavior.

@Andersama
Copy link
Author

Andersama commented Oct 29, 2023

Yeah I've already run into an issue that without upping the warning level [[nodiscard]] isn't helping because the destructor isn't being executed as expected. As of yet*, I've not really used anything interleaved, most of what I've written tends to be nested / paired so this has just made that a bit simpler to do without the headache of asserts being thrown at runtime.

@ocornut ocornut changed the title Is there a header which uses C++ lifetimes to gaurd part of the api? Is there a header which uses C++ lifetimes to guard part of the api? Jun 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants