pixelflut 0.0.4
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 +7 -0
- data/.gitignore +4 -0
- data/README.md +56 -0
- data/bin/pxf +33 -0
- data/bin/pxf-generate +35 -0
- data/bin/pxf-help +27 -0
- data/bin/pxf-server +45 -0
- data/bin/pxf-version +17 -0
- data/gems.rb +3 -0
- data/lib/pixelflut.rb +7 -0
- data/lib/pixelflut/app.rb +79 -0
- data/lib/pixelflut/canvas.rb +4 -0
- data/lib/pixelflut/canvas/base.rb +101 -0
- data/lib/pixelflut/canvas/buffered.rb +29 -0
- data/lib/pixelflut/canvas/color.rb +32 -0
- data/lib/pixelflut/canvas/streamed.rb +20 -0
- data/lib/pixelflut/server.rb +121 -0
- data/lib/pixelflut/text_image.rb +89 -0
- data/lib/pixelflut/version.rb +3 -0
- data/pixelflut.gemspec +0 -0
- data/rakefile.rb +5 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9baf1f1bc33ff7224d647d35d142a7688c9bdc98d3c1195fbc247dfa0bd1c68f
|
4
|
+
data.tar.gz: 98056e9d11872f5856de38e2fcfe3677d55a117cf9850e0ff62c0f862a8fdd73
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5faac48d602c1d8bdc07b1786b5f74732a34c79370f140769ff3dcbc48156d8f433d05c7a62fbc353ca7bee88d697a820dc192c00a694f2f43df9dfe0c606fe7
|
7
|
+
data.tar.gz: e337ed50dfe5e2192f0ee6c16568b8dba2b93a6376ba2e090df8f249a46390e3101797ba7c63f3ebeeeca054453191540a08b27d2c531b107b0338f982961189
|
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Pixelflut
|
2
|
+
|
3
|
+
A Pixelflut server written in Ruby.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
Based on the idea of a simple server protocol to collaborate on a shared canvas named [Pixel Flut](https://cccgoe.de/wiki/Pixelflut) this gem implements a Ruby version.
|
8
|
+
|
9
|
+
This gem is an open experiment to write a fast server and canvas in Ruby. You are welcome to fork or create pull requests to find the fastest solution!
|
10
|
+
|
11
|
+
The gem includes some tools to develop Pixelflut clients and pre-processors.
|
12
|
+
|
13
|
+
### Installation
|
14
|
+
|
15
|
+
Use [Bundler](http://gembundler.com/) to install the gem:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ gem install pixelflut
|
19
|
+
```
|
20
|
+
|
21
|
+
Now the `pxf` command offers the complete functionality.
|
22
|
+
|
23
|
+
### General Help
|
24
|
+
|
25
|
+
You'll find some help on the command line:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
$ pxf
|
29
|
+
usage: pxf command [options]
|
30
|
+
|
31
|
+
valid commands:
|
32
|
+
generate Execute given generator FILEs.
|
33
|
+
help Print help for given COMMAND.
|
34
|
+
server Start Pixelflut server.
|
35
|
+
version Print version information.
|
36
|
+
```
|
37
|
+
|
38
|
+
### Start a server
|
39
|
+
|
40
|
+
Starting the server on default port `1234` and open a drawing screen is quite simple:
|
41
|
+
|
42
|
+
```bash
|
43
|
+
$ pxf server
|
44
|
+
```
|
45
|
+
|
46
|
+
With these options you can configure the server:
|
47
|
+
|
48
|
+
```
|
49
|
+
-b, --bind ADDR bind to given address
|
50
|
+
-p, --port PORT select port(default: 1234)
|
51
|
+
-k, --keep-alive set maximum keep-alive time
|
52
|
+
-r, --read_buffer SIZE set read buffer size (default: 1024)
|
53
|
+
-w, --width WIDTH set canvas width (default: 800)
|
54
|
+
-h, --height HEIGHT set canvas height (default: 600)
|
55
|
+
-f, --[no-]fullscreen run in fullscreen mode
|
56
|
+
```
|
data/bin/pxf
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
def bin_root
|
5
|
+
File.realdirpath('../', __FILE__).freeze
|
6
|
+
end
|
7
|
+
|
8
|
+
def find_commands
|
9
|
+
Hash[Dir.glob(File.join(bin_root, 'pxf-*')).map!{ |cmd| [File.basename(cmd)[4..-1], cmd] }]
|
10
|
+
end
|
11
|
+
|
12
|
+
def err(msg)
|
13
|
+
$stderr.puts("pxf: #{msg}")
|
14
|
+
exit 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def general_help
|
18
|
+
puts('usage: pxf command [options]', nil, 'valid commands:')
|
19
|
+
map = find_commands.transform_values!{ |cmd| %x(#{cmd} --short-help) }
|
20
|
+
cmds = map.keys.sort!
|
21
|
+
len = cmds.max_by(&:size).size + 3
|
22
|
+
cmds.each do |cmd|
|
23
|
+
print cmd.ljust(len)
|
24
|
+
puts(map[cmd])
|
25
|
+
end
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
general_help if ARGV.empty? || ARGV[0] == '--help' || ARGV[0] == '-h'
|
30
|
+
cmd = ARGV.shift
|
31
|
+
fname = File.join(bin_root, 'pxf-' + cmd)
|
32
|
+
err("no such command - #{cmd}") unless File.executable?(fname)
|
33
|
+
exec(fname, *ARGV)
|
data/bin/pxf-generate
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
def help(short)
|
5
|
+
puts('usage: pxf generate FILE [FILE2, ...]', nil) unless short
|
6
|
+
puts('Execute given generator FILEs.')
|
7
|
+
end
|
8
|
+
|
9
|
+
def extract_options(args)
|
10
|
+
files, options = [], {}
|
11
|
+
while !args.empty?
|
12
|
+
arg = args.shift
|
13
|
+
next files << arg unless arg[0] == '-'
|
14
|
+
next options.merge!(Hash[arg[1..-1].chars.map!{ |c| [c.to_sym, true] }]) unless arg[1] == '-'
|
15
|
+
options[arg[2..-1].to_sym] = args.shift
|
16
|
+
end
|
17
|
+
args[0, 0] = files
|
18
|
+
options
|
19
|
+
end
|
20
|
+
|
21
|
+
case ARGV[0]
|
22
|
+
when '--help'
|
23
|
+
help(false)
|
24
|
+
when '--short-help'
|
25
|
+
help(true)
|
26
|
+
else
|
27
|
+
$options = extract_options(ARGV)
|
28
|
+
require File.realdirpath('../../lib/pixelflut/canvas/streamed.rb', __FILE__)
|
29
|
+
begin
|
30
|
+
ARGV.each{ |file| Pixelflut::Canvas::Streamed.new.instance_eval(IO.read(file), file, 0) }
|
31
|
+
print "QUIT\n"
|
32
|
+
rescue Interrupt
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
end
|
data/bin/pxf-help
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
def help(short)
|
5
|
+
puts('usage: pxf help COMMAND', nil) unless short
|
6
|
+
puts('Print help for given COMMAND.')
|
7
|
+
end
|
8
|
+
|
9
|
+
def err(msg)
|
10
|
+
$stderr.puts("pxf: #{msg}")
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
case ARGV[0]
|
15
|
+
when '--help'
|
16
|
+
help(false)
|
17
|
+
when '--short-help'
|
18
|
+
help(true)
|
19
|
+
else
|
20
|
+
root = File.realdirpath('../', __FILE__).freeze
|
21
|
+
exec(File.join(root, 'pxf')) if ARGV.empty?
|
22
|
+
ARGV.each do |cmd|
|
23
|
+
fname = File.join(root, 'pxf-' + cmd)
|
24
|
+
err("no such command - #{cmd}") unless File.executable?(fname)
|
25
|
+
system(fname, '--help')
|
26
|
+
end
|
27
|
+
end
|
data/bin/pxf-server
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
def options(cfg)
|
5
|
+
require 'optparse'
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.summary_indent = ' '
|
8
|
+
opts.banner = 'usage: pxf server [options]'
|
9
|
+
opts.separator(nil)
|
10
|
+
opts.separator('valid options:')
|
11
|
+
opts.on('-b', '--bind ADDR', String, 'bind to given address'){ |v| cfg.server.host = v }
|
12
|
+
opts.on('-p', '--port PORT', Integer, 'select port(default: 1234)'){ |v| cfg.server.port = v }
|
13
|
+
opts.on('-k', '--keep-alive', Float, 'set maximum keep-alive time'){ |v| cfg.server.keep_alive_time = v }
|
14
|
+
opts.on('-r', '--read_buffer SIZE', Integer, 'set read buffer size (default: 1024)'){ |v| cfg.server.read_buffer_size = v }
|
15
|
+
opts.on('-w', '--width WIDTH', Integer, 'set canvas width (default: 800)'){ |v| cfg.width = v }
|
16
|
+
opts.on('-h', '--height HEIGHT', Integer, 'set canvas height (default: 600)'){ |v| cfg.height = v }
|
17
|
+
opts.on('-f', '--[no-]fullscreen', 'run in fullscreen mode'){ |v| cfg.fullscreen = v }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def help(short)
|
22
|
+
puts(options(nil), nil) unless short
|
23
|
+
puts('Start Pixelflut server.')
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
help(false) if ARGV[0] == '--help'
|
28
|
+
help(true) if ARGV[0] == '--short-help'
|
29
|
+
|
30
|
+
def create_configuration
|
31
|
+
cfg = Pixelflut::App::Configuration.default
|
32
|
+
options(cfg).parse!
|
33
|
+
cfg
|
34
|
+
rescue OptionParser::ParseError => e
|
35
|
+
$stderr.puts("pxf: #{e}")
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'optparse'
|
41
|
+
require File.realdirpath('../../lib/pixelflut.rb', __FILE__)
|
42
|
+
Pixelflut::App.run(create_configuration)
|
43
|
+
rescue Interrupt
|
44
|
+
exit
|
45
|
+
end
|
data/bin/pxf-version
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
def help(short)
|
5
|
+
puts('usage: pxf version', nil) unless short
|
6
|
+
puts('Print version information.')
|
7
|
+
end
|
8
|
+
|
9
|
+
case ARGV[0]
|
10
|
+
when '--help'
|
11
|
+
help(false)
|
12
|
+
when '--short-help'
|
13
|
+
help(true)
|
14
|
+
else
|
15
|
+
require File.realdirpath('../../lib/pixelflut/version.rb', __FILE__).freeze
|
16
|
+
puts("pxf #{Pixelflut::VERSION}")
|
17
|
+
end
|
data/gems.rb
ADDED
data/lib/pixelflut.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
module Pixelflut
|
2
|
+
LibDir = File.realdirpath('../pixelflut', __FILE__).freeze
|
3
|
+
autoload :App, File.join(LibDir, 'app.rb')
|
4
|
+
autoload :Server, File.join(LibDir, 'server.rb')
|
5
|
+
autoload :Canvas, File.join(LibDir, 'canvas.rb')
|
6
|
+
autoload :VERSION, File.join(LibDir, 'version.rb')
|
7
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gosu'
|
4
|
+
require_relative 'server'
|
5
|
+
require_relative 'text_image'
|
6
|
+
|
7
|
+
module Pixelflut
|
8
|
+
class App < Gosu::Window
|
9
|
+
Configuration = Struct.new(:width, :height, :fullscreen, :server) do
|
10
|
+
def self.default
|
11
|
+
new(nil, nil, false, Server::Configuration.default)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.run(configuration = Configuration.default)
|
16
|
+
new(configuration).show
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(configuration)
|
20
|
+
if configuration.fullscreen
|
21
|
+
super(configuration.width || Gosu.screen_width, configuration.height || Gosu.screen_height, fullscreen: true)
|
22
|
+
else
|
23
|
+
super(configuration.width || 800, configuration.height || 600)
|
24
|
+
end
|
25
|
+
Process.setproctitle('pxflut')
|
26
|
+
@image = TextImage.new(width, height)
|
27
|
+
@server = Server.new(@image, configuration.server)
|
28
|
+
log(self.caption = "Pixelflut@#{configuration.server.host}:#{configuration.server.port}")
|
29
|
+
reset!
|
30
|
+
end
|
31
|
+
|
32
|
+
def show
|
33
|
+
@server.run
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset!
|
38
|
+
@image.clear
|
39
|
+
log("clean image: #{@image.width}x#{@image.height}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def update
|
43
|
+
@draw_image = nil unless 0 == @image.changes
|
44
|
+
end
|
45
|
+
|
46
|
+
def draw
|
47
|
+
(@draw_image ||= Gosu::Image.new(@image.changed, tileable: true, retro: true)).draw(0, 0, 0)
|
48
|
+
end
|
49
|
+
|
50
|
+
def log(*args)
|
51
|
+
print("[#{Time.now}] ")
|
52
|
+
puts(*args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def button_down(id)
|
56
|
+
return close! if Gosu::Button::KbEscape == id
|
57
|
+
return reset! if Gosu::Button::KbSpace == id
|
58
|
+
return log("connections: #{@server.connection_count}") if Gosu::Button::KbC == id
|
59
|
+
end
|
60
|
+
|
61
|
+
def close
|
62
|
+
close!
|
63
|
+
end
|
64
|
+
|
65
|
+
def needs_redraw?
|
66
|
+
nil == @draw_image
|
67
|
+
end
|
68
|
+
|
69
|
+
def needs_cursor?
|
70
|
+
false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
begin
|
76
|
+
Pixelflut::App.run
|
77
|
+
rescue Interrupt
|
78
|
+
exit
|
79
|
+
end if __FILE__ == $PROGRAM_NAME
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'color'
|
4
|
+
|
5
|
+
module Pixelflut
|
6
|
+
module Canvas
|
7
|
+
class Base
|
8
|
+
attr_accessor :offset_x, :offset_y
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
clear!
|
12
|
+
end
|
13
|
+
|
14
|
+
def clear!
|
15
|
+
@offset_x = @offset_y = 0
|
16
|
+
@color = Color::Black
|
17
|
+
end
|
18
|
+
|
19
|
+
def translate(x, y)
|
20
|
+
ox, oy = @offset_x, @offset_y
|
21
|
+
@offset_x += x
|
22
|
+
@offset_y += y
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
@offset_x, @offset_y = ox, oy
|
26
|
+
end
|
27
|
+
|
28
|
+
def color(color)
|
29
|
+
oc = @color
|
30
|
+
@color = color
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
@color = oc
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(x, y, color)
|
37
|
+
out("PX #{x(x)} #{y(y)} #{color}\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def pix(x, y, color = @color)
|
41
|
+
out("PX #{x(x)} #{y(y)} #{color}\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
def rect(x1, y1, x2, y2, color = @color)
|
45
|
+
out("RC #{x(x1)} #{y(y1)} #{x(x2)} #{y(y2)} #{color}\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
def line(x1, y1, x2, y2, color = @color)
|
49
|
+
return rect(x1, y1, x2, y2, color) if x1 == x2 || y1 == y2
|
50
|
+
x, y, curpixel = x1, y1, 0
|
51
|
+
deltax = (x2 - x1).abs
|
52
|
+
deltay = (y2 - y1).abs
|
53
|
+
xinc1 = xinc2 = x2 >= x1 ? 1 : -1
|
54
|
+
yinc1 = yinc2 = y2 >= y1 ? 1 : -1
|
55
|
+
if deltax >= deltay
|
56
|
+
xinc1 = yinc2 = 0
|
57
|
+
den, numadd, numpixels, num = deltax, deltay, deltax, deltax / 2
|
58
|
+
else
|
59
|
+
xinc2 = yinc1 = 0
|
60
|
+
den, numadd, numpixels, num = deltay, deltax, deltay, deltay / 2
|
61
|
+
end
|
62
|
+
while curpixel <= numpixels
|
63
|
+
num += numadd
|
64
|
+
if num >= den
|
65
|
+
num -= den
|
66
|
+
x += xinc1
|
67
|
+
y += yinc1
|
68
|
+
end
|
69
|
+
x += xinc2
|
70
|
+
y += yinc2
|
71
|
+
pix(x, y, color)
|
72
|
+
curpixel += 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def ascii(x, y, dim = 10, color = @color, pic = nil)
|
77
|
+
pic ||= yield
|
78
|
+
sx = x
|
79
|
+
pic.each_line do |line|
|
80
|
+
line.chomp!
|
81
|
+
line.each_char do |c|
|
82
|
+
rect(x, y, x + dim, y + dim, color) if ' ' != c && '_' != c
|
83
|
+
x += dim
|
84
|
+
end
|
85
|
+
x = sx
|
86
|
+
y += dim
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def x(x)
|
93
|
+
x + @offset_x
|
94
|
+
end
|
95
|
+
|
96
|
+
def y(y)
|
97
|
+
y + @offset_y
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Pixelflut
|
6
|
+
module Canvas
|
7
|
+
class Buffered < Base
|
8
|
+
def clear!
|
9
|
+
@lines = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
return to_enum(__method__) unless block
|
14
|
+
@lines.each(&block)
|
15
|
+
yield "QUIT\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@lines.join + "QUIT\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def out(str)
|
25
|
+
@lines << str
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pixelflut
|
2
|
+
module Canvas
|
3
|
+
class Base
|
4
|
+
module Color
|
5
|
+
class << self
|
6
|
+
def from_rgb(r, g, b)
|
7
|
+
from_rgba(r, g, b, 0xff)
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_rgba(r, g, b, a)
|
11
|
+
as_hex(r) + as_hex(g) + as_hex(b) + as_hex(a)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def as_hex(int)
|
17
|
+
ret = int.to_s(16)
|
18
|
+
ret = '0' + ret if 1 == ret.size
|
19
|
+
ret
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Black = from_rgba(0, 0, 0, 0xff)
|
24
|
+
White = from_rgba(0xff, 0xff, 0xff, 0xff)
|
25
|
+
Red = from_rgba(0xff, 0, 0, 0xff)
|
26
|
+
Green = from_rgba(0, 0xff, 0, 0xff)
|
27
|
+
Blue = from_rgba(0, 0, 0xff, 0xff)
|
28
|
+
Yellow = from_rgba(0xff, 0xff, 0, 0xff)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Pixelflut
|
6
|
+
module Canvas
|
7
|
+
class Streamed < Base
|
8
|
+
def initialize(stream = $stdout)
|
9
|
+
super()
|
10
|
+
@stream = stream
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def out(str)
|
16
|
+
@stream.print(str)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Pixelflut
|
6
|
+
class Server
|
7
|
+
Configuration = Struct.new(
|
8
|
+
:host,
|
9
|
+
:port,
|
10
|
+
:keep_alive_time,
|
11
|
+
:read_buffer_size
|
12
|
+
) do
|
13
|
+
def self.default
|
14
|
+
new(nil, 1234, 1, 1024)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
def initialize(canvas, config = Configuration.default)
|
21
|
+
@canvas, @config = canvas, config
|
22
|
+
@socket, @connections = nil, {}
|
23
|
+
@on_end = ->(conn){ @connections.delete(conn) }
|
24
|
+
@size = "SIZE #{canvas.width} #{canvas.height}\n".freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def connection_count
|
28
|
+
@connections.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def update
|
32
|
+
return create_socket unless @socket
|
33
|
+
now = Time.now.to_f
|
34
|
+
incoming = @socket.accept_nonblock(exception: false)
|
35
|
+
create_connection(incoming, now) unless Symbol === incoming
|
36
|
+
@connections.keys.each{ |con| con.update(now) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
Thread.new do
|
41
|
+
loop{ update }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def create_connection(incoming, now)
|
48
|
+
con = Connection.new(incoming, now, @config, @canvas, @size, @on_end)
|
49
|
+
@connections[con] = con
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_socket
|
53
|
+
@socket = @config.host ? TCPServer.new(@config.host, @config.port) : TCPServer.new(@config.port)
|
54
|
+
@socket.listen(255)
|
55
|
+
end
|
56
|
+
|
57
|
+
class Connection
|
58
|
+
def initialize(socket, now, config, canvas, size, on_end)
|
59
|
+
@socket, @last_tm, @config, @canvas, @size, @on_end = socket, now, config, canvas, size, on_end
|
60
|
+
# @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
61
|
+
@buffer = ''
|
62
|
+
end
|
63
|
+
|
64
|
+
def close(_reason)
|
65
|
+
socket, @socket = @socket, nil
|
66
|
+
return unless socket
|
67
|
+
socket.close
|
68
|
+
@on_end.call(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def update(now)
|
72
|
+
index = @buffer.index("\n")
|
73
|
+
return process_buffer(index, now) if index
|
74
|
+
read_size = @config.read_buffer_size - @buffer.size
|
75
|
+
return close(:buffer_exceeded) if read_size <= 0
|
76
|
+
str = @socket.recv_nonblock(read_size, exception: false)
|
77
|
+
return (now - @last_tm > @config.keep_alive_time ? close(:timeout) : nil) if Symbol === str
|
78
|
+
return close(:closed_by_peer) if 0 == str.size
|
79
|
+
@buffer += str
|
80
|
+
@last_tm = now
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def next_command(index, now)
|
86
|
+
@buffer = @buffer[index, @buffer.size - index]
|
87
|
+
@last_tm = now
|
88
|
+
end
|
89
|
+
|
90
|
+
def command_size(index, now)
|
91
|
+
@socket.sendmsg_nonblock(@size)
|
92
|
+
next_command(index, now)
|
93
|
+
end
|
94
|
+
|
95
|
+
def command_px(command, index, now)
|
96
|
+
_, x, y, color = command.split(' ', 4)
|
97
|
+
return close(:color_expected) unless color
|
98
|
+
@canvas[x.to_i, y.to_i] = color
|
99
|
+
next_command(index, now)
|
100
|
+
end
|
101
|
+
|
102
|
+
def command_rc(command, index, now)
|
103
|
+
_, x1, y1, x2, y2, color = command.split(' ', 6)
|
104
|
+
return close(:color_expected) unless color
|
105
|
+
@canvas.draw_rect(x1.to_i, y1.to_i, x2.to_i, y2.to_i, color)
|
106
|
+
next_command(index, now)
|
107
|
+
end
|
108
|
+
|
109
|
+
def process_buffer(index, now)
|
110
|
+
return close(:max_command_size_exceeded) if index > 31 # 'RC 9999 9999 9999 9999 RRGGBBAA'.size
|
111
|
+
command = @buffer[0, index]
|
112
|
+
index += 1
|
113
|
+
return command_size(index, now) if command == 'SIZE'
|
114
|
+
return close(:quit) if command == 'QUIT'
|
115
|
+
return command_px(command, index, now) if command.start_with?('PX ')
|
116
|
+
return command_rc(command, index, now) if command.start_with?('RC ')
|
117
|
+
close(:bad_command)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pixelflut
|
4
|
+
class TextImage
|
5
|
+
attr_reader :columns, :rows, :to_blob, :changes
|
6
|
+
|
7
|
+
def initialize(width, height)
|
8
|
+
@columns, @rows = width, height
|
9
|
+
@row_inc = width * 4
|
10
|
+
clear
|
11
|
+
end
|
12
|
+
|
13
|
+
alias width columns
|
14
|
+
alias height rows
|
15
|
+
|
16
|
+
def clear
|
17
|
+
@to_blob = Black * (@columns * @rows)
|
18
|
+
@changes = 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def changed
|
22
|
+
@changes = 0
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# def [](x, y)
|
27
|
+
# @data[(4 * (x + @columns * y)) % @data.size, 4].bytes.map! do |b|
|
28
|
+
# b = b.to_s(16)
|
29
|
+
# b = '0' + b if b.size == 1
|
30
|
+
# b
|
31
|
+
# end.join
|
32
|
+
# end
|
33
|
+
|
34
|
+
def []=(x, y, rrggbbaa)
|
35
|
+
@to_blob[(4 * (x + @columns * y)) % @to_blob.size, 4] = as_color(rrggbbaa)
|
36
|
+
@changes += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def draw_rect(x1, y1, x2, y2, rrggbbaa)
|
40
|
+
x1, x2 = x2, x1 if x1 > x2
|
41
|
+
y1, y2 = y2, y1 if y1 > y2
|
42
|
+
color = as_color(rrggbbaa)
|
43
|
+
pos = (4 * (x1 + @columns * y1)) % @data.size
|
44
|
+
pattern = color * (x2 - x1 + 1)
|
45
|
+
(y2 - y1 + 1).times do
|
46
|
+
@data[pos, pattern.size] = pattern
|
47
|
+
pos += @row_inc
|
48
|
+
end
|
49
|
+
@changes += 1
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
ZZ = 0.chr.freeze
|
55
|
+
FF = 0xff.chr.freeze
|
56
|
+
Black = (ZZ + ZZ + ZZ + FF).freeze
|
57
|
+
|
58
|
+
def as_color(rrggbbaa)
|
59
|
+
case rrggbbaa.size
|
60
|
+
when 3 # RGB
|
61
|
+
(rrggbbaa[0] * 2).to_i(16).chr +
|
62
|
+
(rrggbbaa[1] * 2).to_i(16).chr +
|
63
|
+
(rrggbbaa[2] * 2).to_i(16).chr +
|
64
|
+
FF
|
65
|
+
when 4 # RGBA
|
66
|
+
(rrggbbaa[0] * 2).to_i(16).chr +
|
67
|
+
(rrggbbaa[1] * 2).to_i(16).chr +
|
68
|
+
(rrggbbaa[2] * 2).to_i(16).chr +
|
69
|
+
(rrggbbaa[3] * 2).to_i(16).chr
|
70
|
+
when 6 # RRGGBB
|
71
|
+
(rrggbbaa[0, 2]).to_i(16).chr +
|
72
|
+
(rrggbbaa[2, 2]).to_i(16).chr +
|
73
|
+
(rrggbbaa[4, 2]).to_i(16).chr +
|
74
|
+
FF
|
75
|
+
when 8 # RRGGBBAA
|
76
|
+
(rrggbbaa[0, 2]).to_i(16).chr +
|
77
|
+
(rrggbbaa[2, 2]).to_i(16).chr +
|
78
|
+
(rrggbbaa[4, 2]).to_i(16).chr +
|
79
|
+
(rrggbbaa[6, 2]).to_i(16).chr
|
80
|
+
else
|
81
|
+
Black
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# def pos(x, y)
|
86
|
+
# (4 * (x + @columns * y)) % @data.size
|
87
|
+
# end
|
88
|
+
end
|
89
|
+
end
|
data/pixelflut.gemspec
ADDED
Binary file
|
data/rakefile.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pixelflut
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Blumtritt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gosu
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.13.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.13.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.16.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.16.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 12.3.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 12.3.0
|
55
|
+
description: |
|
56
|
+
Based on the idea of a simple server protocol to collaborate on a shared canvas named
|
57
|
+
[Pixel Flut](https://cccgoe.de/wiki/Pixelflut) this gem implements a Ruby version.
|
58
|
+
email: mike.blumtritt@invision.de
|
59
|
+
executables:
|
60
|
+
- pxf
|
61
|
+
- pxf-generate
|
62
|
+
- pxf-server
|
63
|
+
- pxf-help
|
64
|
+
- pxf-version
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files:
|
67
|
+
- README.md
|
68
|
+
files:
|
69
|
+
- ".gitignore"
|
70
|
+
- README.md
|
71
|
+
- bin/pxf
|
72
|
+
- bin/pxf-generate
|
73
|
+
- bin/pxf-help
|
74
|
+
- bin/pxf-server
|
75
|
+
- bin/pxf-version
|
76
|
+
- gems.rb
|
77
|
+
- lib/pixelflut.rb
|
78
|
+
- lib/pixelflut/app.rb
|
79
|
+
- lib/pixelflut/canvas.rb
|
80
|
+
- lib/pixelflut/canvas/base.rb
|
81
|
+
- lib/pixelflut/canvas/buffered.rb
|
82
|
+
- lib/pixelflut/canvas/color.rb
|
83
|
+
- lib/pixelflut/canvas/streamed.rb
|
84
|
+
- lib/pixelflut/server.rb
|
85
|
+
- lib/pixelflut/text_image.rb
|
86
|
+
- lib/pixelflut/version.rb
|
87
|
+
- pixelflut.gemspec
|
88
|
+
- rakefile.rb
|
89
|
+
homepage: https://github.com/mblumtritt/pixelflut
|
90
|
+
licenses: []
|
91
|
+
metadata:
|
92
|
+
issue_tracker: https://github.com/mblumtritt/pixelflut/issues
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.5.0
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 1.3.6
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project: pixelflut
|
109
|
+
rubygems_version: 2.7.3
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: A Pixelflut server written in Ruby.
|
113
|
+
test_files: []
|