pixelflut 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|