io-event 1.12.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15970f96a2af0de9aa23889ee6ff50217b53216334de39ad5b4692ebf945942b
4
- data.tar.gz: 5f44f0dbcdf09106e342e3fda71f56f8a0208dc5a435e82f8c80b1fdb8826fae
3
+ metadata.gz: 53a9942daf957580fe332adea530a3bba659c0ec5405560078233ead0b893d11
4
+ data.tar.gz: d6eea90381836e4f1bf62430ae95ea0f7f2b1693683d2a114b5193f0c7a1d3f8
5
5
  SHA512:
6
- metadata.gz: 0b72815ede642358071d66b9720935603493b6a09a34fcf2eec6930c8648a7d2b6498358f630554f40c6537f2bd0fb3cb6b58334ad58dfbaacc5d6c1f0e407cf
7
- data.tar.gz: a5eb4e8396cd510aa71e8ee45f8ebf60829ca707a96bded1104ce0c3f74c47e9343225d826c58e4ec7bcfed6aed81051d7c14a10f304c42fa61fe930e1099fef
6
+ metadata.gz: 9bfd04d670380038ec6a57a212f0f927b3fd748f93ac95c26e04adb1ed829a999fc02ebca56a27812c84be402b9fef576ba5d53be0df7b8f5e06bb516c70f2b6
7
+ data.tar.gz: ca71eac94412be0bbb953d32eb60ef1654fe24535012bc3f6f9a1a5375377175a71641c28e5ab92a5f3d44418ab50c3ef177a552cc0abe57f633e718cb2b4bc4
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,61 @@
1
+ # Getting Started
2
+
3
+ This guide explains how to use `io-event` for non-blocking IO.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your project:
8
+
9
+ ~~~ bash
10
+ $ bundle add io-event
11
+ ~~~
12
+
13
+ ## Core Concepts
14
+
15
+ `io-event` has several core concepts:
16
+
17
+ - A {ruby IO::Event::Selector} implementation which provides the primitive operations for implementation an event loop.
18
+ - A {ruby IO::Event::Debug::Selector} which adds extra validations and checks at the expense of performance. You should generally use this during tests.
19
+
20
+ ## Basic Event Loop
21
+
22
+ This example shows how to perform a blocking operation
23
+
24
+ ```ruby
25
+ require 'fiber'
26
+ require 'io/event'
27
+
28
+ selector = IO::Event::Selector.new(Fiber.current)
29
+ input, output = IO.pipe
30
+
31
+ writer = Fiber.new do
32
+ output.write("Hello World")
33
+ output.close
34
+ end
35
+
36
+ reader = Fiber.new do
37
+ selector.io_wait(Fiber.current, input, IO::READABLE)
38
+ pp read: input.read
39
+ end
40
+
41
+ # The reader will be blocked until the IO has data available:
42
+ reader.transfer
43
+
44
+ # Write some data to the pipe and close the writing end:
45
+ writer.transfer
46
+
47
+ selector.select(1)
48
+
49
+ # Results in:
50
+ # {:read=>"Hello World"}
51
+ ```
52
+
53
+ ## Debugging
54
+
55
+ The {ruby IO::Event::Debug::Selector} class adds extra validations and checks at the expense of performance. It can also log all operations. You can use this by setting the following environment variables:
56
+
57
+ ```shell
58
+ $ IO_EVENT_SELECTOR_DEBUG=y IO_EVENT_SELECTOR_DEBUG_LOG=/dev/stderr bundle exec ./my_script.rb
59
+ ```
60
+
61
+ The format of the log is subject to change, but it may be useful for debugging.
@@ -0,0 +1,11 @@
1
+ # Automatically generated context index for Utopia::Project guides.
2
+ # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3
+ ---
4
+ description: An event loop.
5
+ metadata:
6
+ documentation_uri: https://socketry.github.io/io-event/
7
+ source_code_uri: https://github.com/socketry/io-event.git
8
+ files:
9
+ - path: getting-started.md
10
+ title: Getting Started
11
+ description: This guide explains how to use `io-event` for non-blocking IO.
data/ext/extconf.rb CHANGED
@@ -19,7 +19,7 @@ append_cflags(["-Wall", "-Wno-unknown-pragmas", "-std=c99"])
19
19
 
20
20
  if ENV.key?("RUBY_DEBUG")
21
21
  $stderr.puts "Enabling debug mode..."
22
-
22
+
23
23
  append_cflags(["-DRUBY_DEBUG", "-O0"])
24
24
  end
25
25
 
@@ -33,7 +33,7 @@ have_func("&rb_fiber_transfer")
33
33
  if have_library("uring") and have_header("liburing.h")
34
34
  # We might want to consider using this in the future:
35
35
  # have_func("io_uring_submit_and_wait_timeout", "liburing.h")
36
-
36
+
37
37
  $srcs << "io/event/selector/uring.c"
38
38
  end
39
39
 
@@ -70,7 +70,7 @@ end
70
70
 
71
71
  if ENV.key?("RUBY_SANITIZE")
72
72
  $stderr.puts "Enabling sanitizers..."
73
-
73
+
74
74
  # Add address and undefined behaviour sanitizers:
75
75
  append_cflags(["-fsanitize=address", "-fsanitize=undefined", "-fno-omit-frame-pointer"])
76
76
  $LDFLAGS << " -fsanitize=address -fsanitize=undefined"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2024, by Samuel Williams.
4
+ # Copyright, 2021-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "../support"
7
7
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2021, by Wander Hillen.
5
- # Copyright, 2021-2024, by Samuel Williams.
5
+ # Copyright, 2021-2025, by Samuel Williams.
6
6
 
7
7
  class IO
8
8
  module Event
@@ -28,6 +28,11 @@ class IO
28
28
  @contents.size
29
29
  end
30
30
 
31
+ # @returns [Boolean] true if the heap is empty, false otherwise.
32
+ def empty?
33
+ @contents.empty?
34
+ end
35
+
31
36
  # Removes and returns the smallest element in the heap, or nil if the heap is empty.
32
37
  #
33
38
  # @returns [Object | Nil] The smallest element in the heap, or nil if the heap is empty.
@@ -74,19 +79,112 @@ class IO
74
79
  return self
75
80
  end
76
81
 
82
+ # Add multiple elements to the heap efficiently in O(n) time.
83
+ # This is more efficient than calling push multiple times (O(n log n)).
84
+ #
85
+ # @parameter elements [Array] The elements to add to the heap.
86
+ # @returns [self] Returns self for method chaining.
87
+ def concat(elements)
88
+ return self if elements.empty?
89
+
90
+ # Add all elements to the array without maintaining heap property - O(n)
91
+ @contents.concat(elements)
92
+
93
+ # Rebuild the heap property for the entire array - O(n)
94
+ heapify!
95
+
96
+ return self
97
+ end
98
+
77
99
  # Empties out the heap, discarding all elements
78
100
  def clear!
79
101
  @contents = []
80
102
  end
81
103
 
104
+ # Remove a specific element from the heap.
105
+ #
106
+ # O(n) where n is the number of elements in the heap.
107
+ #
108
+ # @parameter element [Object] The element to remove.
109
+ # @returns [Object | Nil] The removed element, or nil if not found.
110
+ def delete(element)
111
+ # Find the index of the element - O(n) linear search
112
+ index = @contents.index(element)
113
+ return nil unless index
114
+
115
+ # If it's the last element, just remove it
116
+ if index == @contents.size - 1
117
+ return @contents.pop
118
+ end
119
+
120
+ # Store the value we're removing
121
+ removed_value = @contents[index]
122
+
123
+ # Replace with the last element
124
+ last = @contents.pop
125
+ @contents[index] = last
126
+
127
+ # Restore heap property - might need to bubble up or down
128
+ if index > 0 && @contents[index] < @contents[(index - 1) / 2]
129
+ # New element is smaller than parent, bubble up
130
+ bubble_up(index)
131
+ else
132
+ # New element might be larger than children, bubble down
133
+ bubble_down(index)
134
+ end
135
+
136
+ # validate!
137
+
138
+ return removed_value
139
+ end
140
+
141
+ # Remove elements matching the given block condition by rebuilding the heap.
142
+ #
143
+ # This is more efficient than multiple delete operations when removing many elements.
144
+ #
145
+ # O(n) where n is the number of elements in the heap.
146
+ #
147
+ # @yields [Object] Each element in the heap for testing
148
+ # @returns [Integer] The number of elements removed
149
+ def delete_if
150
+ return enum_for(:delete_if) unless block_given?
151
+
152
+ original_size = @contents.size
153
+
154
+ # Filter out elements that match the condition - O(n)
155
+ @contents.reject! {|element| yield(element)}
156
+
157
+ # If we removed elements, rebuild the heap - O(n)
158
+ if @contents.size < original_size
159
+ heapify!
160
+ end
161
+
162
+ # Return number of elements removed
163
+ original_size - @contents.size
164
+ end
165
+
82
166
  # Validate the heap invariant. Every element except the root must not be smaller than its parent element. Note that it MAY be equal.
83
167
  def valid?
84
- # Notice we skip index 0 on purpose, because it has no parent
168
+ # Notice we skip index 0 on purpose, because it has no parent:
85
169
  (1..(@contents.size - 1)).all? {|index| @contents[index] >= @contents[(index - 1) / 2]}
86
170
  end
87
171
 
88
172
  private
89
173
 
174
+ # Rebuild the heap property from an arbitrary array in O(n) time.
175
+ # Uses bottom-up heapify algorithm starting from the last non-leaf node.
176
+ def heapify!
177
+ return if @contents.size <= 1
178
+
179
+ # Start from the last non-leaf node and work backwards to root:
180
+ last_non_leaf_index = (@contents.size / 2) - 1
181
+ last_non_leaf_index.downto(0) do |index|
182
+ bubble_down(index)
183
+ end
184
+
185
+ # validate!
186
+ end
187
+
90
188
  # Left here for reference, but unused.
91
189
  # def swap(i, j)
92
190
  # @contents[i], @contents[j] = @contents[j], @contents[i]
@@ -363,7 +363,7 @@ module IO::Event
363
363
  priority << io
364
364
  end
365
365
  end
366
-
366
+
367
367
  false
368
368
  end
369
369
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2024, by Samuel Williams.
4
+ # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  class IO
7
7
  module Event
@@ -7,6 +7,6 @@
7
7
  class IO
8
8
  # @namespace
9
9
  module Event
10
- VERSION = "1.12.1"
10
+ VERSION = "1.14.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -18,6 +18,10 @@ Please see the [project documentation](https://socketry.github.io/io-event/) for
18
18
 
19
19
  Please see the [project releases](https://socketry.github.io/io-event/releases/index) for all releases.
20
20
 
21
+ ### v1.14.0
22
+
23
+ - [Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods](https://socketry.github.io/io-event/releases/index#enhanced-io::event::priorityheap-with-deletion-and-bulk-insertion-methods)
24
+
21
25
  ### v1.11.2
22
26
 
23
27
  - Fix Windows build.
data/releases.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Releases
2
2
 
3
+ ## v1.14.0
4
+
5
+ ### Enhanced `IO::Event::PriorityHeap` with deletion and bulk insertion methods
6
+
7
+ The {ruby IO::Event::PriorityHeap} now supports efficient element removal and bulk insertion:
8
+
9
+ - **`delete(element)`**: Remove a specific element from the heap in O(n) time
10
+ - **`delete_if(&block)`**: Remove elements matching a condition with O(n) amortized bulk deletion
11
+ - **`concat(elements)`**: Add multiple elements efficiently in O(n) time
12
+
13
+ <!-- end list -->
14
+
15
+ ``` ruby
16
+ heap = IO::Event::PriorityHeap.new
17
+
18
+ # Efficient bulk insertion - O(n) instead of O(n log n)
19
+ heap.concat([5, 2, 8, 1, 9, 3])
20
+
21
+ # Remove specific element
22
+ removed = heap.delete(5) # Returns 5, heap maintains order
23
+
24
+ # Bulk removal with condition
25
+ count = heap.delete_if { |x| x.even? } # Removes 2, 8 efficiently
26
+ ```
27
+
28
+ The `delete_if` and `concat` methods are particularly efficient for bulk operations, using bottom-up heapification to maintain the heap property in O(n) time. This provides significant performance improvements:
29
+
30
+ - **Bulk insertion**: O(n log n) → O(n) for adding multiple elements
31
+ - **Bulk deletion**: O(k×n) → O(n) for removing k elements
32
+
33
+ Both methods maintain the heap invariant and include comprehensive test coverage with edge case validation.
34
+
3
35
  ## v1.11.2
4
36
 
5
37
  - Fix Windows build.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.1
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -55,6 +55,8 @@ extensions:
55
55
  extra_rdoc_files: []
56
56
  files:
57
57
  - agent.md
58
+ - context/getting-started.md
59
+ - context/index.yaml
58
60
  - design.md
59
61
  - ext/extconf.rb
60
62
  - ext/io/event/array.h
@@ -114,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
116
  - !ruby/object:Gem::Version
115
117
  version: '0'
116
118
  requirements: []
117
- rubygems_version: 3.6.7
119
+ rubygems_version: 3.7.0.dev
118
120
  specification_version: 4
119
121
  summary: An event loop.
120
122
  test_files: []
metadata.gz.sig CHANGED
Binary file