ruby-sfml 3.0.0.0 → 3.0.0.1
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 +4 -4
- data/CHANGELOG.md +130 -0
- data/README.md +34 -45
- data/lib/sfml/audio/internal.rb +41 -0
- data/lib/sfml/audio/listener.rb +31 -0
- data/lib/sfml/audio/music.rb +64 -0
- data/lib/sfml/audio/sound.rb +82 -0
- data/lib/sfml/audio/sound_cone.rb +56 -0
- data/lib/sfml/audio/sound_stream.rb +206 -0
- data/lib/sfml/c/audio.rb +78 -0
- data/lib/sfml/c/graphics.rb +51 -0
- data/lib/sfml/c/network.rb +104 -0
- data/lib/sfml/c/system.rb +10 -0
- data/lib/sfml/c/window.rb +42 -0
- data/lib/sfml/graphics/image.rb +23 -0
- data/lib/sfml/graphics/render_states.rb +5 -3
- data/lib/sfml/graphics/render_target.rb +23 -2
- data/lib/sfml/graphics/render_window.rb +55 -0
- data/lib/sfml/graphics/shader.rb +71 -11
- data/lib/sfml/graphics/stencil_mode.rb +93 -0
- data/lib/sfml/graphics/vertex_buffer.rb +127 -0
- data/lib/sfml/network/ftp.rb +184 -0
- data/lib/sfml/network/http.rb +117 -0
- data/lib/sfml/network/socket_selector.rb +93 -0
- data/lib/sfml/version.rb +1 -1
- data/lib/sfml/window/event.rb +14 -0
- data/lib/sfml/window/sensor.rb +50 -0
- data/lib/sfml/window/touch.rb +39 -0
- data/lib/sfml/window/window.rb +57 -0
- data/lib/sfml.rb +27 -2
- metadata +11 -1
|
@@ -15,8 +15,23 @@ module SFML
|
|
|
15
15
|
# through the right CSFML draw function for whichever target they're
|
|
16
16
|
# being rendered to.
|
|
17
17
|
module RenderTarget
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
# Clear the target's colour buffer (and optionally the stencil
|
|
19
|
+
# buffer in the same call). Pass `stencil:` to clear the stencil
|
|
20
|
+
# buffer to the given integer; pass only `stencil:` to clear the
|
|
21
|
+
# stencil without touching colour.
|
|
22
|
+
#
|
|
23
|
+
# target.clear # default black
|
|
24
|
+
# target.clear(SFML::Color.cornflower_blue) # only colour
|
|
25
|
+
# target.clear(SFML::Color.black, stencil: 0) # both
|
|
26
|
+
# target.clear(stencil: 0) # only stencil
|
|
27
|
+
def clear(color = nil, stencil: nil)
|
|
28
|
+
if stencil && color
|
|
29
|
+
_csfml(:clearColorAndStencil, @handle, color.to_native, _stencil_value(stencil))
|
|
30
|
+
elsif stencil
|
|
31
|
+
_csfml(:clearStencil, @handle, _stencil_value(stencil))
|
|
32
|
+
else
|
|
33
|
+
_csfml(:clear, @handle, (color || Color::BLACK).to_native)
|
|
34
|
+
end
|
|
20
35
|
self
|
|
21
36
|
end
|
|
22
37
|
|
|
@@ -141,6 +156,12 @@ module SFML
|
|
|
141
156
|
def _csfml(suffix, *args)
|
|
142
157
|
C::Graphics.public_send(:"#{self.class::CSFML_PREFIX}_#{suffix}", *args)
|
|
143
158
|
end
|
|
159
|
+
|
|
160
|
+
def _stencil_value(int)
|
|
161
|
+
v = C::Graphics::StencilValue.new
|
|
162
|
+
v[:value] = Integer(int)
|
|
163
|
+
v
|
|
164
|
+
end
|
|
144
165
|
end
|
|
145
166
|
end
|
|
146
167
|
end
|
|
@@ -112,6 +112,52 @@ module SFML
|
|
|
112
112
|
Vector2.new(v[:x], v[:y])
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
# Replace the window's title-bar / taskbar icon with the pixels from
|
|
116
|
+
# the given SFML::Image. The OS scales it as needed; 32×32 RGBA
|
|
117
|
+
# is the typical sweet spot.
|
|
118
|
+
def icon=(image)
|
|
119
|
+
raise ArgumentError, "RenderWindow#icon= requires a SFML::Image" unless image.is_a?(Image)
|
|
120
|
+
|
|
121
|
+
size = C::System::Vector2u.new
|
|
122
|
+
size[:x] = image.width
|
|
123
|
+
size[:y] = image.height
|
|
124
|
+
C::Graphics.sfRenderWindow_setIcon(@handle, size, C::Graphics.sfImage_getPixelsPtr(image.handle))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Constrain user-driven resizes. Accepts a [w, h] Array, a Vector2,
|
|
128
|
+
# or nil to clear the limit. When set, the OS won't let the user
|
|
129
|
+
# drag the window smaller (or larger) than this — programmatic
|
|
130
|
+
# `size=` is not affected.
|
|
131
|
+
def minimum_size=(value)
|
|
132
|
+
C::Graphics.sfRenderWindow_setMinimumSize(@handle, _vec2u_or_nil(value))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def maximum_size=(value)
|
|
136
|
+
C::Graphics.sfRenderWindow_setMaximumSize(@handle, _vec2u_or_nil(value))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# OS-specific native handle for the underlying window — `HWND` on
|
|
140
|
+
# Windows, `NSView*` on macOS, X11 `Window` xid on Linux.
|
|
141
|
+
def native_handle
|
|
142
|
+
C::Graphics.sfRenderWindow_getNativeHandle(@handle)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Wrap an existing OS-level window. `handle` is a platform native
|
|
146
|
+
# handle (Integer address or FFI::Pointer). Useful for embedding
|
|
147
|
+
# the renderer inside another framework's window (Qt, Gtk, raw
|
|
148
|
+
# Win32, NSView).
|
|
149
|
+
def self.from_handle(handle)
|
|
150
|
+
ptr = handle.is_a?(FFI::Pointer) ? handle : FFI::Pointer.new(:void, Integer(handle))
|
|
151
|
+
raw = C::Graphics.sfRenderWindow_createFromHandle(ptr, nil)
|
|
152
|
+
raise Error, "sfRenderWindow_createFromHandle returned NULL" if raw.null?
|
|
153
|
+
|
|
154
|
+
win = allocate
|
|
155
|
+
win.instance_variable_set(:@handle,
|
|
156
|
+
FFI::AutoPointer.new(raw, C::Graphics.method(:sfRenderWindow_destroy)))
|
|
157
|
+
win.instance_variable_set(:@event_buffer, C::Window::Event.new)
|
|
158
|
+
win
|
|
159
|
+
end
|
|
160
|
+
|
|
115
161
|
# Convenience driver loop. Yields the per-frame delta (SFML::Time) and
|
|
116
162
|
# auto-pumps events + display. The block is responsible for #clear and
|
|
117
163
|
# any drawing.
|
|
@@ -136,6 +182,15 @@ module SFML
|
|
|
136
182
|
|
|
137
183
|
private
|
|
138
184
|
|
|
185
|
+
def _vec2u_or_nil(value)
|
|
186
|
+
return nil if value.nil?
|
|
187
|
+
|
|
188
|
+
vec = value.is_a?(Vector2) ? value : Vector2.new(*value)
|
|
189
|
+
v = C::System::Vector2u.new
|
|
190
|
+
v[:x] = Integer(vec.x); v[:y] = Integer(vec.y)
|
|
191
|
+
v
|
|
192
|
+
end
|
|
193
|
+
|
|
139
194
|
def parse_args(args)
|
|
140
195
|
case args.length
|
|
141
196
|
when 2
|
data/lib/sfml/graphics/shader.rb
CHANGED
|
@@ -28,8 +28,17 @@ module SFML
|
|
|
28
28
|
# [a, b, c] → vec3
|
|
29
29
|
# [a, b, c, d] → vec4
|
|
30
30
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
31
|
+
# Array uniforms (`uniform vec2 positions[8];` and friends):
|
|
32
|
+
# [[x, y], [x, y], ...] → vec2[]
|
|
33
|
+
# [[x, y, z], ...] → vec3[]
|
|
34
|
+
# [[x, y, z, w], ...] → vec4[]
|
|
35
|
+
# [Vector2[a, b], ...] → vec2[] (also accepts Vector2 / Vector3)
|
|
36
|
+
#
|
|
37
|
+
# Float arrays (`uniform float weights[N];`) are ambiguous with vec3
|
|
38
|
+
# at length 3, so use the explicit `#set_float_array` setter.
|
|
39
|
+
#
|
|
40
|
+
# Need an int / bvec / matrix uniform? Use `#set_int`, `#set_ivec2`,
|
|
41
|
+
# etc. — they exist for completeness.
|
|
33
42
|
class Shader
|
|
34
43
|
# Class-level: is GLSL available on the current GPU at all?
|
|
35
44
|
def self.available?
|
|
@@ -85,16 +94,23 @@ module SFML
|
|
|
85
94
|
when :current_texture
|
|
86
95
|
C::Graphics.sfShader_setCurrentTextureUniform(@handle, n)
|
|
87
96
|
when Array
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
v[:x] = value[0].to_f; v[:y] = value[1].to_f
|
|
94
|
-
v[:z] = value[2].to_f; v[:w] = value[3].to_f
|
|
95
|
-
C::Graphics.sfShader_setVec4Uniform(@handle, n, v)
|
|
97
|
+
raise ArgumentError, "Shader uniform array must not be empty" if value.empty?
|
|
98
|
+
|
|
99
|
+
first = value.first
|
|
100
|
+
if first.is_a?(Array) || first.is_a?(Vector2) || first.is_a?(Vector3)
|
|
101
|
+
_set_vec_array_uniform(n, value)
|
|
96
102
|
else
|
|
97
|
-
|
|
103
|
+
case value.length
|
|
104
|
+
when 2 then self[name] = Vector2.new(*value)
|
|
105
|
+
when 3 then self[name] = Vector3.new(*value)
|
|
106
|
+
when 4
|
|
107
|
+
v = C::Graphics::GlslVec4.new
|
|
108
|
+
v[:x] = value[0].to_f; v[:y] = value[1].to_f
|
|
109
|
+
v[:z] = value[2].to_f; v[:w] = value[3].to_f
|
|
110
|
+
C::Graphics.sfShader_setVec4Uniform(@handle, n, v)
|
|
111
|
+
else
|
|
112
|
+
raise ArgumentError, "Shader uniform array must be length 2, 3, or 4 (got #{value.length})"
|
|
113
|
+
end
|
|
98
114
|
end
|
|
99
115
|
else
|
|
100
116
|
raise ArgumentError,
|
|
@@ -114,8 +130,52 @@ module SFML
|
|
|
114
130
|
C::Graphics.sfShader_setIvec2Uniform(@handle, name.to_s, v)
|
|
115
131
|
end
|
|
116
132
|
|
|
133
|
+
# Set a `uniform float arr[N];` from a plain Ruby array of numbers.
|
|
134
|
+
# Float arrays can't be inferred via `[]=` because they'd collide
|
|
135
|
+
# with the vec3 case at length 3.
|
|
136
|
+
def set_float_array(name, values)
|
|
137
|
+
buf = FFI::MemoryPointer.new(:float, values.length)
|
|
138
|
+
buf.write_array_of_float(values.map(&:to_f))
|
|
139
|
+
C::Graphics.sfShader_setFloatUniformArray(@handle, name.to_s, buf, values.length)
|
|
140
|
+
end
|
|
141
|
+
|
|
117
142
|
attr_reader :handle # :nodoc:
|
|
118
143
|
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
# Detect the inner length (2/3/4), pack a contiguous float buffer,
|
|
147
|
+
# and dispatch to the matching CSFML setVec*UniformArray.
|
|
148
|
+
def _set_vec_array_uniform(name, elements)
|
|
149
|
+
raise ArgumentError, "uniform array must not be empty" if elements.empty?
|
|
150
|
+
|
|
151
|
+
flat = elements.flat_map do |el|
|
|
152
|
+
case el
|
|
153
|
+
when Vector2 then [el.x.to_f, el.y.to_f]
|
|
154
|
+
when Vector3 then [el.x.to_f, el.y.to_f, el.z.to_f]
|
|
155
|
+
when Array then el.map(&:to_f)
|
|
156
|
+
else
|
|
157
|
+
raise ArgumentError, "uniform array element must be Array/Vector2/Vector3 (got #{el.class})"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
stride = flat.length / elements.length
|
|
162
|
+
raise ArgumentError, "uniform array elements must all be the same length" \
|
|
163
|
+
unless flat.length == stride * elements.length
|
|
164
|
+
|
|
165
|
+
buf = FFI::MemoryPointer.new(:float, flat.length)
|
|
166
|
+
buf.write_array_of_float(flat)
|
|
167
|
+
|
|
168
|
+
case stride
|
|
169
|
+
when 2 then C::Graphics.sfShader_setVec2UniformArray(@handle, name, buf, elements.length)
|
|
170
|
+
when 3 then C::Graphics.sfShader_setVec3UniformArray(@handle, name, buf, elements.length)
|
|
171
|
+
when 4 then C::Graphics.sfShader_setVec4UniformArray(@handle, name, buf, elements.length)
|
|
172
|
+
else
|
|
173
|
+
raise ArgumentError, "uniform array elements must be length 2, 3, or 4 (got #{stride})"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
public
|
|
178
|
+
|
|
119
179
|
# @!visibility private
|
|
120
180
|
def self._wrap(ptr)
|
|
121
181
|
shader = allocate
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# How a draw call interacts with the stencil buffer. The classic
|
|
3
|
+
# use is masking — first you draw a "mask" shape that writes a
|
|
4
|
+
# value into the stencil buffer, then you draw the masked content
|
|
5
|
+
# with a comparison that only lets pixels through where the
|
|
6
|
+
# stencil matches.
|
|
7
|
+
#
|
|
8
|
+
# target.clear(SFML::Color.black, stencil: 0)
|
|
9
|
+
#
|
|
10
|
+
# # Phase 1 — write the mask shape into the stencil buffer.
|
|
11
|
+
# write = SFML::StencilMode.new(
|
|
12
|
+
# comparison: :always, update_operation: :replace, reference: 1,
|
|
13
|
+
# only_write_mask: true, # don't actually paint colour
|
|
14
|
+
# )
|
|
15
|
+
# target.draw(mask_circle, stencil_mode: write)
|
|
16
|
+
#
|
|
17
|
+
# # Phase 2 — draw the content; only pixels where stencil == 1
|
|
18
|
+
# # survive.
|
|
19
|
+
# read = SFML::StencilMode.new(
|
|
20
|
+
# comparison: :equal, update_operation: :keep, reference: 1,
|
|
21
|
+
# )
|
|
22
|
+
# target.draw(scene, stencil_mode: read)
|
|
23
|
+
#
|
|
24
|
+
# `stencilOnly` (`only_write_mask:`) is a niche flag — set it to
|
|
25
|
+
# true on the mask-write pass if you don't want the mask shape
|
|
26
|
+
# itself to be visible in the colour buffer.
|
|
27
|
+
class StencilMode
|
|
28
|
+
# Order matches sfStencilComparison in CSFML 3.
|
|
29
|
+
COMPARISONS = %i[never less less_equal greater greater_equal equal not_equal always].freeze
|
|
30
|
+
COMPARISON_INDEX = COMPARISONS.each_with_index.to_h.freeze
|
|
31
|
+
|
|
32
|
+
# Order matches sfStencilUpdateOperation in CSFML 3.
|
|
33
|
+
OPERATIONS = %i[keep zero replace increment decrement invert].freeze
|
|
34
|
+
OPERATION_INDEX = OPERATIONS.each_with_index.to_h.freeze
|
|
35
|
+
|
|
36
|
+
attr_reader :comparison, :update_operation, :reference, :mask, :only_write_mask
|
|
37
|
+
|
|
38
|
+
def initialize(comparison: :always, update_operation: :keep,
|
|
39
|
+
reference: 0, mask: 0xFFFFFFFF, only_write_mask: false)
|
|
40
|
+
raise ArgumentError, "Unknown stencil comparison: #{comparison.inspect}" \
|
|
41
|
+
unless COMPARISON_INDEX.key?(comparison)
|
|
42
|
+
raise ArgumentError, "Unknown stencil update_operation: #{update_operation.inspect}" \
|
|
43
|
+
unless OPERATION_INDEX.key?(update_operation)
|
|
44
|
+
|
|
45
|
+
@comparison = comparison
|
|
46
|
+
@update_operation = update_operation
|
|
47
|
+
@reference = Integer(reference)
|
|
48
|
+
@mask = Integer(mask)
|
|
49
|
+
@only_write_mask = !!only_write_mask
|
|
50
|
+
freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ==(other)
|
|
54
|
+
other.is_a?(StencilMode) &&
|
|
55
|
+
comparison == other.comparison &&
|
|
56
|
+
update_operation == other.update_operation &&
|
|
57
|
+
reference == other.reference &&
|
|
58
|
+
mask == other.mask &&
|
|
59
|
+
only_write_mask == other.only_write_mask
|
|
60
|
+
end
|
|
61
|
+
alias eql? ==
|
|
62
|
+
def hash = [comparison, update_operation, reference, mask, only_write_mask].hash
|
|
63
|
+
|
|
64
|
+
def to_s
|
|
65
|
+
"StencilMode(#{comparison}, #{update_operation}, ref=#{reference}, " \
|
|
66
|
+
"mask=#{format('0x%08X', mask)}, only_write=#{only_write_mask})"
|
|
67
|
+
end
|
|
68
|
+
alias inspect to_s
|
|
69
|
+
|
|
70
|
+
# @!visibility private
|
|
71
|
+
# Write our fields into a CSFML StencilMode struct (typically a
|
|
72
|
+
# sub-struct of an existing RenderStates buffer).
|
|
73
|
+
def populate(struct)
|
|
74
|
+
struct[:comparison] = COMPARISON_INDEX[@comparison]
|
|
75
|
+
struct[:update_operation] = OPERATION_INDEX[@update_operation]
|
|
76
|
+
struct[:reference][:value] = @reference
|
|
77
|
+
struct[:mask][:value] = @mask
|
|
78
|
+
struct[:only_write_mask] = @only_write_mask
|
|
79
|
+
struct
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @!visibility private
|
|
83
|
+
def self.from_native(struct)
|
|
84
|
+
new(
|
|
85
|
+
comparison: COMPARISONS[struct[:comparison]],
|
|
86
|
+
update_operation: OPERATIONS[struct[:update_operation]],
|
|
87
|
+
reference: struct[:reference][:value],
|
|
88
|
+
mask: struct[:mask][:value],
|
|
89
|
+
only_write_mask: struct[:only_write_mask],
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# GPU-resident vertex buffer. Same shape as VertexArray but the
|
|
3
|
+
# vertices live in video memory, so a draw call only ships an
|
|
4
|
+
# index/handle instead of re-uploading the geometry every frame.
|
|
5
|
+
# Useful for static meshes, large tilemaps, particle systems with
|
|
6
|
+
# mostly-static positions.
|
|
7
|
+
#
|
|
8
|
+
# buf = SFML::VertexBuffer.new(
|
|
9
|
+
# vertices,
|
|
10
|
+
# primitive_type: :triangles,
|
|
11
|
+
# usage: :static,
|
|
12
|
+
# )
|
|
13
|
+
# window.draw(buf)
|
|
14
|
+
#
|
|
15
|
+
# Update later (full or partial):
|
|
16
|
+
#
|
|
17
|
+
# buf.update(new_vertices) # full replace
|
|
18
|
+
# buf.update(some_vertices, offset: 32) # patch in place
|
|
19
|
+
#
|
|
20
|
+
# If the GPU doesn't support OpenGL vertex buffer objects,
|
|
21
|
+
# `SFML::VertexBuffer.available?` returns false and you should
|
|
22
|
+
# fall back to `VertexArray`.
|
|
23
|
+
class VertexBuffer
|
|
24
|
+
USAGES = C::Graphics::VERTEX_BUFFER_USAGES
|
|
25
|
+
USAGE_INDEX = USAGES.each_with_index.to_h.freeze
|
|
26
|
+
|
|
27
|
+
PRIMITIVE_TYPES = VertexArray::PRIMITIVE_TYPES
|
|
28
|
+
PRIMITIVE_INDEX = VertexArray::PRIMITIVE_INDEX
|
|
29
|
+
|
|
30
|
+
def self.available?
|
|
31
|
+
C::Graphics.sfVertexBuffer_isAvailable
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Build a buffer either from an Array of vertices or by giving an
|
|
35
|
+
# explicit `count:` to allocate empty (then fill via #update).
|
|
36
|
+
def initialize(vertices = nil, count: nil, primitive_type: :points, usage: :stream)
|
|
37
|
+
n = vertices ? vertices.length : Integer(count || 0)
|
|
38
|
+
raise ArgumentError, "give either `vertices` or `count:`" if n.zero? && vertices.nil?
|
|
39
|
+
|
|
40
|
+
ptype = PRIMITIVE_INDEX.fetch(primitive_type) do
|
|
41
|
+
raise ArgumentError,
|
|
42
|
+
"Unknown primitive type: #{primitive_type.inspect} " \
|
|
43
|
+
"(expected one of #{PRIMITIVE_TYPES.inspect})"
|
|
44
|
+
end
|
|
45
|
+
uidx = USAGE_INDEX.fetch(usage) do
|
|
46
|
+
raise ArgumentError,
|
|
47
|
+
"Unknown VertexBuffer usage: #{usage.inspect} " \
|
|
48
|
+
"(expected one of #{USAGES.inspect})"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
ptr = C::Graphics.sfVertexBuffer_create(n, ptype, uidx)
|
|
52
|
+
raise Error, "sfVertexBuffer_create returned NULL " \
|
|
53
|
+
"(VBOs unavailable on this GPU?)" if ptr.null?
|
|
54
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfVertexBuffer_destroy))
|
|
55
|
+
|
|
56
|
+
update(vertices) if vertices && !vertices.empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def count = C::Graphics.sfVertexBuffer_getVertexCount(@handle)
|
|
60
|
+
alias size count
|
|
61
|
+
alias length count
|
|
62
|
+
|
|
63
|
+
def primitive_type
|
|
64
|
+
PRIMITIVE_TYPES[C::Graphics.sfVertexBuffer_getPrimitiveType(@handle)] || :unknown
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def primitive_type=(type)
|
|
68
|
+
idx = PRIMITIVE_INDEX.fetch(type) do
|
|
69
|
+
raise ArgumentError, "Unknown primitive type: #{type.inspect}"
|
|
70
|
+
end
|
|
71
|
+
C::Graphics.sfVertexBuffer_setPrimitiveType(@handle, idx)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def usage
|
|
75
|
+
USAGES[C::Graphics.sfVertexBuffer_getUsage(@handle)] || :unknown
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def usage=(value)
|
|
79
|
+
idx = USAGE_INDEX.fetch(value) do
|
|
80
|
+
raise ArgumentError, "Unknown VertexBuffer usage: #{value.inspect}"
|
|
81
|
+
end
|
|
82
|
+
C::Graphics.sfVertexBuffer_setUsage(@handle, idx)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Replace `vertices.length` slots starting at `offset`. Pass an
|
|
86
|
+
# array longer than the buffer to grow it (CSFML expands when
|
|
87
|
+
# offset==0 and length > current).
|
|
88
|
+
def update(vertices, offset: 0)
|
|
89
|
+
n = vertices.length
|
|
90
|
+
buf = FFI::MemoryPointer.new(C::Graphics::Vertex, n)
|
|
91
|
+
vertices.each_with_index do |v, i|
|
|
92
|
+
slot = C::Graphics::Vertex.new(buf + i * C::Graphics::Vertex.size)
|
|
93
|
+
slot[:position][:x] = v.position.x.to_f
|
|
94
|
+
slot[:position][:y] = v.position.y.to_f
|
|
95
|
+
slot[:color][:r] = v.color.r
|
|
96
|
+
slot[:color][:g] = v.color.g
|
|
97
|
+
slot[:color][:b] = v.color.b
|
|
98
|
+
slot[:color][:a] = v.color.a
|
|
99
|
+
slot[:tex_coords][:x] = v.tex_coords.x.to_f
|
|
100
|
+
slot[:tex_coords][:y] = v.tex_coords.y.to_f
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
ok = C::Graphics.sfVertexBuffer_update(@handle, buf, n, Integer(offset))
|
|
104
|
+
raise Error, "sfVertexBuffer_update failed " \
|
|
105
|
+
"(buffer too small or driver rejected the upload?)" unless ok
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def native_handle = C::Graphics.sfVertexBuffer_getNativeHandle(@handle)
|
|
110
|
+
|
|
111
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
112
|
+
target._draw_native(:VertexBuffer, @handle, states_ptr)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Draw a slice of this buffer instead of the whole thing. Useful
|
|
116
|
+
# when you've packed several meshes back-to-back and want to
|
|
117
|
+
# render one at a time.
|
|
118
|
+
def draw_range_on(target, first, count, states_ptr = nil)
|
|
119
|
+
C::Graphics.public_send(
|
|
120
|
+
:"#{target.class::CSFML_PREFIX}_drawVertexBufferRange",
|
|
121
|
+
target.handle, @handle, Integer(first), Integer(count), states_ptr,
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
attr_reader :handle # :nodoc:
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Network
|
|
3
|
+
# CSFML's FTP client. Useful for the rare game that needs to fetch
|
|
4
|
+
# extra content from a plain-FTP server. For anything modern,
|
|
5
|
+
# Ruby's stdlib `Net::FTP` (and the Net::FTP gem) is a much nicer
|
|
6
|
+
# tool — this binding exists for parity with CSFML.
|
|
7
|
+
#
|
|
8
|
+
# ftp = SFML::Network::Ftp.new
|
|
9
|
+
# ftp.connect("ftp.example.com").ok? #=> true
|
|
10
|
+
# ftp.login_anonymous.ok? #=> true
|
|
11
|
+
# ftp.directory_listing("/").names #=> ["pub", "incoming", ...]
|
|
12
|
+
# ftp.download("/pub/file.bin", "/tmp/")
|
|
13
|
+
# ftp.disconnect
|
|
14
|
+
#
|
|
15
|
+
# Each call returns a Response (or DirectoryResponse / ListingResponse
|
|
16
|
+
# for commands that return a path or a list). All responses expose
|
|
17
|
+
# `#ok?`, `#status` (Integer), `#status_symbol`, `#message`.
|
|
18
|
+
class Ftp
|
|
19
|
+
DEFAULT_PORT = 21
|
|
20
|
+
DEFAULT_TIMEOUT = SFML::Time.zero
|
|
21
|
+
|
|
22
|
+
# FTP status codes mapped to symbols. The transport-error ones
|
|
23
|
+
# at ≥ 1000 are SFML's, not RFC 959.
|
|
24
|
+
STATUS_NAMES = {
|
|
25
|
+
110 => :restart_marker_reply, 120 => :service_ready_soon,
|
|
26
|
+
125 => :data_connection_already_opened, 150 => :opening_data_connection,
|
|
27
|
+
200 => :ok, 211 => :system_status, 212 => :directory_status,
|
|
28
|
+
213 => :file_status, 214 => :help_message,
|
|
29
|
+
215 => :system_type, 220 => :service_ready, 221 => :closing_connection,
|
|
30
|
+
225 => :data_connection_opened, 226 => :closing_data_connection,
|
|
31
|
+
227 => :entering_passive_mode, 230 => :logged_in,
|
|
32
|
+
250 => :file_action_ok, 257 => :directory_ok,
|
|
33
|
+
331 => :need_password, 332 => :need_account_to_log_in,
|
|
34
|
+
350 => :need_information,
|
|
35
|
+
421 => :service_unavailable, 425 => :data_connection_unavailable,
|
|
36
|
+
426 => :transfer_aborted, 450 => :file_action_aborted,
|
|
37
|
+
451 => :local_error, 452 => :insufficient_storage_space,
|
|
38
|
+
500 => :command_unknown, 501 => :parameters_unknown,
|
|
39
|
+
502 => :command_not_implemented, 503 => :bad_command_sequence,
|
|
40
|
+
504 => :parameter_not_implemented, 530 => :not_logged_in,
|
|
41
|
+
532 => :need_account_to_store, 550 => :file_unavailable,
|
|
42
|
+
551 => :page_type_unknown, 552 => :not_enough_memory,
|
|
43
|
+
553 => :filename_not_allowed,
|
|
44
|
+
1000 => :invalid_response, 1001 => :connection_failed,
|
|
45
|
+
1002 => :connection_closed, 1003 => :invalid_file,
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
def initialize
|
|
49
|
+
ptr = C::Network.sfFtp_create
|
|
50
|
+
raise Error, "sfFtp_create returned NULL" if ptr.null?
|
|
51
|
+
|
|
52
|
+
@handle = FFI::AutoPointer.new(ptr, C::Network.method(:sfFtp_destroy))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Connect to an FTP server. `host` may be an IP string ("1.2.3.4")
|
|
56
|
+
# or hostname ("ftp.example.com"); pass timeout as a SFML::Time
|
|
57
|
+
# or numeric seconds (default 0 = no timeout).
|
|
58
|
+
def connect(host, port: DEFAULT_PORT, timeout: DEFAULT_TIMEOUT)
|
|
59
|
+
addr = IpAddress.from_string(host).struct
|
|
60
|
+
t = timeout.is_a?(Time) ? timeout : Time.seconds(timeout.to_f)
|
|
61
|
+
Response._take_ownership(C::Network.sfFtp_connect(@handle, addr, Integer(port), t.to_native))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def login_anonymous
|
|
65
|
+
Response._take_ownership(C::Network.sfFtp_loginAnonymous(@handle))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def login(user, password)
|
|
69
|
+
Response._take_ownership(C::Network.sfFtp_login(@handle, user.to_s, password.to_s))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def disconnect = Response._take_ownership(C::Network.sfFtp_disconnect(@handle))
|
|
73
|
+
def keep_alive = Response._take_ownership(C::Network.sfFtp_keepAlive(@handle))
|
|
74
|
+
|
|
75
|
+
def working_directory
|
|
76
|
+
DirectoryResponse._take_ownership(C::Network.sfFtp_getWorkingDirectory(@handle))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def directory_listing(directory = "")
|
|
80
|
+
ListingResponse._take_ownership(C::Network.sfFtp_getDirectoryListing(@handle, directory.to_s))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def change_directory(directory)
|
|
84
|
+
Response._take_ownership(C::Network.sfFtp_changeDirectory(@handle, directory.to_s))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parent_directory
|
|
88
|
+
Response._take_ownership(C::Network.sfFtp_parentDirectory(@handle))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create_directory(name)
|
|
92
|
+
DirectoryResponse._take_ownership(C::Network.sfFtp_createDirectory(@handle, name.to_s))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def delete_directory(name)
|
|
96
|
+
Response._take_ownership(C::Network.sfFtp_deleteDirectory(@handle, name.to_s))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def rename_file(file, new_name)
|
|
100
|
+
Response._take_ownership(C::Network.sfFtp_renameFile(@handle, file.to_s, new_name.to_s))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def delete_file(name)
|
|
104
|
+
Response._take_ownership(C::Network.sfFtp_deleteFile(@handle, name.to_s))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def download(remote, local, mode: :binary)
|
|
108
|
+
idx = C::Network::FTP_TRANSFER_MODES.index(mode) ||
|
|
109
|
+
raise(ArgumentError, "Unknown FTP transfer mode: #{mode.inspect}")
|
|
110
|
+
Response._take_ownership(C::Network.sfFtp_download(@handle, remote.to_s, local.to_s, idx))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def upload(local, remote, mode: :binary, append: false)
|
|
114
|
+
idx = C::Network::FTP_TRANSFER_MODES.index(mode) ||
|
|
115
|
+
raise(ArgumentError, "Unknown FTP transfer mode: #{mode.inspect}")
|
|
116
|
+
Response._take_ownership(C::Network.sfFtp_upload(@handle, local.to_s, remote.to_s, idx, !!append))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def send_command(command, parameter = "")
|
|
120
|
+
Response._take_ownership(C::Network.sfFtp_sendCommand(@handle, command.to_s, parameter.to_s))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
attr_reader :handle # :nodoc:
|
|
124
|
+
|
|
125
|
+
# Generic response: most FTP operations return this.
|
|
126
|
+
class Response
|
|
127
|
+
def ok? = C::Network.sfFtpResponse_isOk(@handle)
|
|
128
|
+
def status = C::Network.sfFtpResponse_getStatus(@handle)
|
|
129
|
+
def status_symbol = STATUS_NAMES[status] || status
|
|
130
|
+
def message = C::Network.sfFtpResponse_getMessage(@handle).to_s
|
|
131
|
+
attr_reader :handle # :nodoc:
|
|
132
|
+
|
|
133
|
+
# @!visibility private
|
|
134
|
+
def self._take_ownership(ptr)
|
|
135
|
+
obj = allocate
|
|
136
|
+
obj.instance_variable_set(:@handle,
|
|
137
|
+
FFI::AutoPointer.new(ptr, C::Network.method(:sfFtpResponse_destroy)))
|
|
138
|
+
obj
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Returned by working_directory and create_directory — adds the
|
|
143
|
+
# `#directory` accessor on top of Response.
|
|
144
|
+
class DirectoryResponse
|
|
145
|
+
def ok? = C::Network.sfFtpDirectoryResponse_isOk(@handle)
|
|
146
|
+
def status = C::Network.sfFtpDirectoryResponse_getStatus(@handle)
|
|
147
|
+
def status_symbol = STATUS_NAMES[status] || status
|
|
148
|
+
def message = C::Network.sfFtpDirectoryResponse_getMessage(@handle).to_s
|
|
149
|
+
def directory = C::Network.sfFtpDirectoryResponse_getDirectory(@handle).to_s
|
|
150
|
+
attr_reader :handle # :nodoc:
|
|
151
|
+
|
|
152
|
+
# @!visibility private
|
|
153
|
+
def self._take_ownership(ptr)
|
|
154
|
+
obj = allocate
|
|
155
|
+
obj.instance_variable_set(:@handle,
|
|
156
|
+
FFI::AutoPointer.new(ptr, C::Network.method(:sfFtpDirectoryResponse_destroy)))
|
|
157
|
+
obj
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returned by directory_listing — adds the `#names` array.
|
|
162
|
+
class ListingResponse
|
|
163
|
+
def ok? = C::Network.sfFtpListingResponse_isOk(@handle)
|
|
164
|
+
def status = C::Network.sfFtpListingResponse_getStatus(@handle)
|
|
165
|
+
def status_symbol = STATUS_NAMES[status] || status
|
|
166
|
+
def message = C::Network.sfFtpListingResponse_getMessage(@handle).to_s
|
|
167
|
+
def count = C::Network.sfFtpListingResponse_getCount(@handle)
|
|
168
|
+
attr_reader :handle # :nodoc:
|
|
169
|
+
|
|
170
|
+
def names
|
|
171
|
+
Array.new(count) { |i| C::Network.sfFtpListingResponse_getName(@handle, i).to_s }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @!visibility private
|
|
175
|
+
def self._take_ownership(ptr)
|
|
176
|
+
obj = allocate
|
|
177
|
+
obj.instance_variable_set(:@handle,
|
|
178
|
+
FFI::AutoPointer.new(ptr, C::Network.method(:sfFtpListingResponse_destroy)))
|
|
179
|
+
obj
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|