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

Add support for Gio async functions. #157

Merged
merged 2 commits into from
Jul 11, 2024
Merged
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ So if you want to rescue from this specific error you must `rescue e : GLib::Fil
error in this domain you must `rescue e : GLib::FileError`, and finally if you want to rescue from any GLib errors you do
`rescue e : GLib::GLibError`.

## Gio Async Pattern

All `*_async` methods with a `*_finish` methods receive a block, the block works as the `Gio::AsyncReadyCallback` and you need
to call the `*_finish` on the `result`, exceptions are raised by the `*_finish` functions on errors.

Example:

```Crystal
file = Gio::File.new_for_path("/my/nice/file")
file.read_async(0, nil) do |obj, result|
obj.as(Gio::File).read_finish(result)
end
```

## Raw C Structs

At [binding.yml](BINDING_YML.md) file you can define the strategy used to bind the structs, if set to `auto`it will behave
Expand Down
58 changes: 53 additions & 5 deletions src/generator/arg_strategy.cr
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ module Generator
CrystalToC
end

property? remove_from_declaration : Bool
property? remove_from_declaration : Bool = false
property? capture_block : Bool = false
getter method : CallableInfo
getter arg : ArgInfo

Expand All @@ -32,6 +33,7 @@ module Generator
def self.find_strategies(method : CallableInfo, direction : Direction) : Array(ArgStrategy)
strategies = method.args.map { |arg| ArgStrategy.new(method, arg) }
{% for plan_class in %w(CallbackArgPlan
AsyncPatternArgPlan
TransferFullArgPlan
ArrayLengthArgPlan
OutArgUsedInReturnPlan
Expand Down Expand Up @@ -60,7 +62,6 @@ module Generator
end

def initialize(@method : CallableInfo, @arg : ArgInfo)
@remove_from_declaration = false
end

def has_implementation? : Bool
Expand All @@ -78,12 +79,18 @@ module Generator
def render_declaration(io : IO)
return if remove_from_declaration?

# Default arg declaration
null_mark = '?' if arg.nullable?
# ⚠️`capture_block` flag is used for GIO async methods, the generator must
# create another version of the method with the block capture since `&callback : Proc(Nil)?
# trigger a compiler error.
#
# As the generator needs a refactor to make it easier to add method overloads based on argument
# strategies, for now I just set the captured block as non-nilable.
null_mark = '?' if arg.nullable? && !capture_block?
type = to_crystal_type(arg_type, is_arg: true)
name = to_crystal_arg_decl(arg.name)
io << '&' if capture_block?
io << name << " : " << type << null_mark
io << ','
io << ',' if !capture_block? # Crystal bug(?) parsing foo(&capture : Proc(Nil),).
end

def add_implementation(arg_plan : ArgPlan, direction : Direction) : Nil
Expand Down Expand Up @@ -446,4 +453,45 @@ module Generator
def generate_c_to_crystal_implementation(io : IO, strategy : ArgStrategy) : Nil
end
end

struct AsyncPatternArgPlan < ArgPlan
def match?(strategy : ArgStrategy, direction : ArgStrategy::Direction) : Bool
arg = strategy.arg
type_info = arg.type_info
callback = type_info.interface
return false unless callback.is_a?(CallbackInfo)
return false if callback.name != "AsyncReadyCallback" || callback.namespace.name != "Gio"

idx = strategies.index(strategy)
return false if idx.nil? || idx != strategies.size - 2

user_data_arg = strategies[idx + 1].arg
return false unless user_data_arg.type_info.tag.void?

strategy.capture_block = true
strategies[idx + 1].remove_from_declaration = true
end

def generate_crystal_to_c_implementation(io : IO, strategy : ArgStrategy) : Nil
arg = strategy.arg
type_info = arg.type_info
idx = strategies.index(strategy).not_nil!

callback_var = to_identifier(arg.name)
user_data_var = to_identifier(strategies[idx + 1].arg.name)
io << user_data_var << " = ::Box.box(" << callback_var << ")\n"
io << callback_var << " = if " << callback_var << ".nil?\n"
io << " Pointer(Void).null\n"
io << "else\n"
io << " ->(gobject : Void*, result : Void*, box : Void*) {\n"
io << " unboxed_callback = ::Box(Gio::AsyncReadyCallback).unbox(box)\n"
io << " GICrystal::ClosureDataManager.deregister(box)\n"
io << " unboxed_callback.call(typeof(self).new(gobject, :none), Gio::AbstractAsyncResult.new(result, :none))\n"
io << " }.pointer\n"
io << "end\n"
end

def generate_c_to_crystal_implementation(io : IO, strategy : ArgStrategy) : Nil
end
end
end