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

Implement "placement new"-style constructors generation #79

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion dear_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ def convert_header(

mod_set_arguments_as_nullable.apply(dom_root, ["fmt"], False) # All arguments called "fmt" are non-nullable
mod_remove_operators.apply(dom_root)
mod_remove_heap_constructors_and_destructors.apply(dom_root)
mod_convert_references_to_pointers.apply(dom_root)
if no_struct_by_value_arguments:
mod_convert_by_value_struct_args_to_pointers.apply(dom_root)
Expand All @@ -246,6 +245,22 @@ def convert_header(
'ImRect',
'ImGuiListClipperRange'
])

# Mark certain types as needing placement new constructors that initialize the memory block passed into them
mod_mark_placement_constructor_structs.apply(dom_root, [
Copy link
Contributor

Choose a reason for hiding this comment

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

this list was given by @ocornut . lets not depend on that.
how can i figuire out this list and/or knows when to update it?

Copy link
Contributor

Choose a reason for hiding this comment

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

the ask is to provide additional detail in a comment.

Copy link
Member

Choose a reason for hiding this comment

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

I don't list we can automatically figure out what goes in the list.
I can try to notify this project if new types appear that are likely to require constructors, otherwise people will eventually request them.

'ImFontConfig',
'ImFontGlyphRangesBuilder',
'ImGuiListClipper',
'ImGuiSelectionBasicStorage',
'ImGuiSelectionExternalStorage',
'ImGuiStorage',
'ImGuiTextBuffer',
'ImGuiTextFilter',
'ImGuiWindowClass',
])

mod_remove_heap_constructors_and_destructors.apply(dom_root)

mod_mark_internal_members.apply(dom_root)
mod_flatten_class_functions.apply(dom_root)
mod_flatten_inheritance.apply(dom_root)
Expand Down
1 change: 1 addition & 0 deletions docs/MetadataFormat.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ for reference, but without any internal details.
| original_fully_qualified_name | The original C++ name of the structure |
| kind _(previously "type")_ | The type of the structure (either `struct` or `union`) |
| by_value | Is this structure normally pass-by-value? |
| has_placement_constructor | Does this structure have an initializer function? |
| forward_declaration | Is this a forward-declaration of the structure? |
| is_anonymous | Is this an anonymous struct? |
| fields | List of contained fields |
Expand Down
1 change: 1 addition & 0 deletions src/code_dom/classstructunion.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def __init__(self):
self.is_anonymous = True
self.is_forward_declaration = True
self.is_by_value = False # Is this to be passed by value? (set during modification)
self.has_placement_constructor = False # Do we need to generate a placement new style constructor to initialize default values?
self.structure_type = None # Will be "STRUCT", "CLASS" or "UNION"
self.is_imgui_api = False # Does this use IMGUI_API?
self.base_classes = None # List of base classes, as tuples with their accessibility (i.e. ("private", "CBase"))
Expand Down
1 change: 1 addition & 0 deletions src/code_dom/functiondeclaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(self):
self.is_operator = False
self.is_constructor = False
self.is_by_value_constructor = False # Is this a by-value type constructor? (set during flattening)
self.is_placement_constructor = False # Is this a 'placement new'-style constructor? (set during flattening)
self.is_destructor = False
self.is_imgui_api = False
self.im_fmtargs = None
Expand Down
49 changes: 30 additions & 19 deletions src/generators/gen_function_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s
is_const_function = False
self_class_type = function.original_class
# Constructors are a special case as they don't get self passed in
if self_class_type is not None and not function.is_constructor and not function.is_static:
if self_class_type is not None and (not function.is_constructor or function.is_placement_constructor) and not function.is_static:
has_self = True
# The function's own is_const will be false as it has been transformed into a non-const stub, but the
# self argument will be const in the case it was originally const
Expand Down Expand Up @@ -220,23 +220,28 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s
# Generate return type cast if necessary

if function.is_constructor:
# Constructors are a special case that returns the type they are constructing
# 'placement new' style constructors don't return anything
if function.is_placement_constructor:
return_cast_prefix = ""
return_cast_suffix = ""
else:
# Constructors are a special case that returns the type they are constructing

# To use generate_cast() we need to generate a type element that represents what the C++ new() call will
# be returning
original_type_name = original_function.get_parent_class().get_fully_qualified_name()
# To use generate_cast() we need to generate a type element that represents what the C++ new() call will
# be returning
original_type_name = original_function.get_parent_class().get_fully_qualified_name()

if not function.is_by_value_constructor:
original_type_name += "*"
if not function.is_by_value_constructor:
original_type_name += "*"

new_type = code_dom.DOMType()
new_type.tokens = utils.create_tokens_for_type(original_type_name)
new_type = code_dom.DOMType()
new_type.tokens = utils.create_tokens_for_type(original_type_name)

return_cast_prefix, return_cast_suffix = generate_cast(new_type,
function.return_type,
imgui_custom_types,
nested_classes,
to_cpp=False)
return_cast_prefix, return_cast_suffix = generate_cast(new_type,
function.return_type,
imgui_custom_types,
nested_classes,
to_cpp=False)
else:
return_cast_prefix, return_cast_suffix = generate_cast(original_function.return_type,
function.return_type,
Expand All @@ -255,16 +260,22 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s
# <function name>V function that takes a va_list
function_call_name += "V"

self_pointer_cast = None
if has_self:
# Cast self pointer
if is_const_function:
thunk_call += "reinterpret_cast<const " + \
self_pointer_cast = "reinterpret_cast<const " + \
self_class_type.get_original_fully_qualified_name(include_leading_colons=True) + \
"*>(self)->"
"*>(self)"
else:
thunk_call += "reinterpret_cast<" + \
self_pointer_cast = "reinterpret_cast<" + \
self_class_type.get_original_fully_qualified_name(include_leading_colons=True) + \
"*>(self)->"
"*>(self)"

if function.is_placement_constructor:
thunk_call += "IM_PLACEMENT_NEW(" + self_pointer_cast + ") "
else:
thunk_call += self_pointer_cast + "->"
else:
# If the function is not a member function, prefix the call with :: to avoid accidentally picking
# up functions from the wrong namespace
Expand All @@ -278,7 +289,7 @@ def generate(dom_root, file, imgui_custom_types, indent=0, custom_varargs_list_s
thunk_call += "&"

if function.is_constructor:
if not function.is_by_value_constructor:
if not function.is_by_value_constructor and not function.is_placement_constructor:
# Add new (unless this is by-value, in which case we don't want it)
thunk_call += "new "
# Constructor calls use the typename, not the nominal function name within the type
Expand Down
1 change: 1 addition & 0 deletions src/generators/gen_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ def emit_struct(struct):
result["original_fully_qualified_name"] = struct.get_original_fully_qualified_name()
result["kind"] = struct.structure_type.lower() # Lowercase this for consistency with C
result["by_value"] = struct.is_by_value
result["has_placement_constructor"] = struct.has_placement_constructor
result["forward_declaration"] = struct.is_forward_declaration
result["is_anonymous"] = struct.is_anonymous

Expand Down
1 change: 1 addition & 0 deletions src/modifiers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from . import mod_rename_function_by_signature
from . import mod_forward_declare_structs
from . import mod_mark_by_value_structs
from . import mod_mark_placement_constructor_structs
from . import mod_add_includes
from . import mod_change_includes
from . import mod_remove_includes
Expand Down
19 changes: 18 additions & 1 deletion src/modifiers/mod_flatten_class_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def apply(dom_root):
current_add_point = struct

is_by_value = struct.is_by_value
has_placement_constructor = struct.has_placement_constructor

# Find any child functions
# Note that this doesn't handle functions in nested classes correctly
Expand All @@ -23,15 +24,31 @@ def apply(dom_root):
if function.is_constructor:
# Constructors get modified to return a pointer to the newly-created object
function.return_type = code_dom.DOMType()
function.return_type.parent = function
if is_by_value:
# By-value types have constructors that return by-value, unsurprisingly
function.return_type.tokens = [utils.create_token(struct.name)]
elif has_placement_constructor:
function.return_type.tokens = [utils.create_token("void")]

# Add a self argument as the first argument of the function
self_arg = code_dom.DOMFunctionArgument()
self_arg.parent = function
self_arg.name = "self"
self_arg.arg_type = code_dom.DOMType()
self_arg.arg_type.parent = self_arg

self_arg.is_instance_pointer = True
self_arg.arg_type.tokens = [utils.create_token(struct.name), utils.create_token("*")]

function.arguments.insert(0, self_arg)
else:
function.return_type.tokens = [utils.create_token(struct.name),
utils.create_token("*")]
function.return_type.parent = function

# Make a note for the code generator that this is a by-value constructor
function.is_by_value_constructor = is_by_value
function.is_placement_constructor = has_placement_constructor
elif function.is_destructor:
if is_by_value:
# We don't support this for fairly obvious reasons
Expand Down
10 changes: 10 additions & 0 deletions src/modifiers/mod_mark_placement_constructor_structs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from src import code_dom
from src import utils


# This modifier adds a marker to structs that should be treated as having a placement new style constructor,
# which subsequent modifiers (and the code generator) can use
def apply(dom_root, has_placement_constructor_structs):
for struct in dom_root.list_all_children_of_type(code_dom.DOMClassStructUnion):
if struct.name in has_placement_constructor_structs:
struct.has_placement_constructor = True
4 changes: 2 additions & 2 deletions src/modifiers/mod_remove_heap_constructors_and_destructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@


# This modifier removes constructions and destructors that would result in heap allocations
# (i.e. those not on value types)
# (i.e. those not on value types or types marked as having placement new constructors)
def apply(dom_root):
for function in dom_root.list_all_children_of_type(code_dom.DOMFunctionDeclaration):
if function.is_constructor or function.is_destructor:
parent_class = function.get_parent_class()
if (parent_class is not None) and (not parent_class.is_by_value):
if (parent_class is not None) and (not parent_class.is_by_value) and (not parent_class.has_placement_constructor):
function.parent.remove_child(function)