Now with even more pixels!

This commit is contained in:
Marcel Hellkamp 2014-04-21 02:02:25 +02:00
parent ab69b8f86f
commit 5911691434
2 changed files with 88 additions and 75 deletions

View file

@ -20,18 +20,19 @@ text = 'P1XELFLUT! v%s (%d)\n' % (
pixelflut.__version__, pixelflut.__version__,
os.stat(__file__).st_mtime) os.stat(__file__).st_mtime)
text += '$ echo "HELP" | netcat %s %d\n' % (IP, port) text += '$ echo "HELP" | netcat %s %d\n' % (IP, port)
text += 'https://github.com/defnull/pixelflut' text += 'https://github.com/defnull/pixelflut\n'
help = 'Commands:' help = 'Commands:\n'
help += '>>> HELP\n' help += '>>> HELP\n'
help += '>>> SIZE\n' help += '>>> SIZE\n'
help += '>>> TEXT x y text\n' help += '>>> QUIT\n'
help += '>>> PX x y [RRGGBB (hex)]\n' help += '>>> TEXT x y text (currently disabled)\n'
help += '>>> PX x y [RRGGBB[AA]]\n'
@on('LOAD') @on('LOAD')
def callback(c): def callback(c):
c.load_font('./font.png') c.load_font('./font.png')
c.set_title('@ %s:%d' % (IP, port)) c.set_title('@ %s:%d' % (IP, c.port))
@on('UNLOAD') @on('UNLOAD')
def callback(canvas): def callback(canvas):
@ -44,7 +45,6 @@ def on_resize(c):
@on('CONNECT') @on('CONNECT')
def on_connect(c, client): def on_connect(c, client):
pass pass
#print client
@on('KEYDOWN-c') @on('KEYDOWN-c')
def on_key_c(c): def on_key_c(c):
@ -59,7 +59,10 @@ def on_key_s(c):
c.save_as(mask%i) c.save_as(mask%i)
c.text(5,5, 'Saved as %s' % mask % i) c.text(5,5, 'Saved as %s' % mask % i)
@on('KEYDOWN-k')
def on_key_k(c):
for ip, client in c.clients.iteritems():
client.disconnect()
@on('COMMAND-HELP') @on('COMMAND-HELP')
def on_help(canvas, client): def on_help(canvas, client):
@ -69,7 +72,7 @@ def on_help(canvas, client):
def on_text(canvas, client, x, y, *words): def on_text(canvas, client, x, y, *words):
x, y = int(x), int(y) x, y = int(x), int(y)
text = ' '.join(words)[:200] text = ' '.join(words)[:200]
canvas.text(x, y, text, delay=0.5) canvas.text(x, y, text, delay=1)
@on('COMMAND-SIZE') @on('COMMAND-SIZE')
def on_size(canvas, client): def on_size(canvas, client):
@ -79,11 +82,17 @@ def on_size(canvas, client):
def on_quit(canvas, client): def on_quit(canvas, client):
client.disconnect() client.disconnect()
@on('COMMAND-GODMODE')
def on_quit(canvas, client, mode):
if mode == 'on':
client.pps = 100000
else:
client.pps = 1000
@on('COMMAND-PX') @on('COMMAND-PX')
def on_px(canvas, client, x, y, color=None): def on_px(canvas, client, x, y, color=None):
global pixelcount global pixelcount
pixelcount += 1 pixelcount += 1
client.last_pixel = time.time()
x, y = int(x), int(y) x, y = int(x), int(y)
if color: if color:
c = int(color, 16) c = int(color, 16)
@ -107,16 +116,28 @@ def on_px(canvas, client, x, y, color=None):
last_save = 0 last_save = 0
status_text = ''
import math
@on('TICK') @on('TICK')
def on_tick(canvas, dt): def on_tick(canvas):
global last_save, pixelcount global last_save, pixelcount, status_text
canvas.text(5, 5, text, delay=0)
canvas.text(5, 5, status_text, delay=0)
ccount = len([c for c in canvas.clients.values() if c.socket])
if ccount > 300:
for ip, client in canvas.clients.iteritems():
client.disconnect()
if time.time() > last_save: if time.time() > last_save:
status_text = text
status_text += 'px/s: %d \n' % (pixelcount / 5)
status_text += 'Connections: %d' % ccount
pixelcount = 0
last_save = time.time() + 5 last_save = time.time() + 5
canvas.save_as('save/mov_%d.png' % last_save) canvas.save_as('save/mov_%d.png' % last_save)
print len(canvas.clients)
canvas.text(5, 200, 'px/s %d' % (pixelcount/5), delay=0)
canvas.text(5, 208, 'Connections %d' % len([c for c in canvas.clients.values() if c.socket]), delay=0)
pixelcount = 0

View file

@ -3,8 +3,8 @@
__version__ = '0.6' __version__ = '0.6'
import time import time
from gevent import spawn, sleep as gsleep from gevent import spawn, sleep as gsleep, GreenletExit
from gevent.socket import socket from gevent.socket import socket, SOL_SOCKET, SO_REUSEADDR
from gevent.coros import Semaphore, RLock from gevent.coros import Semaphore, RLock
from gevent.queue import Queue from gevent.queue import Queue
from collections import deque from collections import deque
@ -22,25 +22,23 @@ log = logging.getLogger('pixelflut')
async = spawn async = spawn
class Client(object): class Client(object):
px_per_tick = 100 pps = 1000
def __init__(self, canvas): def __init__(self, canvas):
self.canvas = canvas self.canvas = canvas
self.socket = None self.socket = None
self.connect_ts = time.time() self.connect_ts = time.time()
# This buffer discards all but the newest 1024 messages
self.sendbuffer = deque([], 1024)
# And this is used to limit clients to X messages per tick # And this is used to limit clients to X messages per tick
# We start at 0 (instead of x) to add a reconnect-penalty. # We start at 0 (instead of x) to add a reconnect-penalty.
self.limit = Semaphore(0)
self.lock = RLock() self.lock = RLock()
def send(self, line): def send(self, line):
self.sendbuffer.append(line.strip() + '\n') with self.lock:
if self.socket:
self.socket.sendall(line + '\n')
def nospam(self, line): def nospam(self, line):
if not self.sendbuffer: self.send(line)
self.sendbuffer.append(line.strip() + '\n')
def disconnect(self): def disconnect(self):
with self.lock: with self.lock:
@ -48,39 +46,29 @@ class Client(object):
socket = self.socket socket = self.socket
self.socket = None self.socket = None
socket.close() socket.close()
log.info('Disconnect') self.canvas.fire('DISCONNECT', self)
def serve(self, socket): def serve(self, socket):
self.canvas.fire('CONNECT', self)
with self.lock: with self.lock:
self.socket = socket self.socket = socket
sendall = self.socket.sendall
readline = self.socket.makefile().readline readline = self.socket.makefile().readline
try: try:
while self.socket: while self.socket:
self.limit.acquire() gsleep(10.0/self.pps)
# Idea: Send first, receive later. If the client is to for i in range(10):
# slow to get the send-buffer empty, he cannot send. line = readline(1024).strip()
while self.sendbuffer: if not line:
sendall(self.sendbuffer.popleft()) break
line = readline().strip() arguments = line.split()
if not line: command = arguments.pop(0)
break if not self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments):
arguments = line.split() self.disconnect()
command = arguments.pop(0)
try:
self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments)
except Exception, e:
socket.send('ERROR %r :(' % e)
break
finally: finally:
self.disconnect() self.disconnect()
def tick(self):
while self.limit.counter <= self.px_per_tick:
self.limit.release()
@ -94,7 +82,7 @@ class Canvas(object):
pygame.mixer.quit() pygame.mixer.quit()
self.set_title() self.set_title()
self.screen = pygame.display.set_mode(self.size, self.flags) self.screen = pygame.display.set_mode(self.size, self.flags)
self.ticks = 0 self.frames = 0
self.width = self.screen.get_width() self.width = self.screen.get_width()
self.height = self.screen.get_height() self.height = self.screen.get_height()
self.clients = {} self.clients = {}
@ -108,33 +96,31 @@ class Canvas(object):
spawn(self._loop) spawn(self._loop)
self.socket = socket() self.socket = socket()
self.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
self.socket.bind((host, port)) self.socket.bind((host, port))
self.socket.listen(500) self.socket.listen(100)
while True: while True:
sock, addr = self.socket.accept() sock, addr = self.socket.accept()
ip, port = addr ip, port = addr
log.info('Connect %s:%d', ip, port)
client = self.clients.get(ip) client = self.clients.get(ip)
if not client: if client:
client.disconnect()
client.task.kill()
else:
client = self.clients[ip] = Client(self) client = self.clients[ip] = Client(self)
client.disconnect() client.task = spawn(client.serve, sock)
spawn(self.handle_client, client, sock)
def handle_client(self, client, sock):
try:
self.fire('CONNECT', client)
client.serve(sock) # This blocks until ready
self.fire('DISCONNECT', client)
finally:
client.disconnect()
def _loop(self): def _loop(self):
doptim = 1.0 / 30
flip = pygame.display.flip
getevents = pygame.event.get
while True: while True:
gsleep(0.01) # Required to allow other tasks to run t1 = time.time()
for e in pygame.event.get():
for e in getevents():
if e.type == pygame.VIDEORESIZE: if e.type == pygame.VIDEORESIZE:
old = self.screen.copy() old = self.screen.copy()
self.screen = pygame.display.set_mode(e.size, self.flags) self.screen = pygame.display.set_mode(e.size, self.flags)
@ -146,11 +132,14 @@ class Canvas(object):
return return
elif e.type == pygame.KEYDOWN: elif e.type == pygame.KEYDOWN:
self.fire('KEYDOWN-' + e.unicode) self.fire('KEYDOWN-' + e.unicode)
self.ticks += 1
self.fire('TICK', self.ticks) self.fire('TICK')
for c in self.clients.values(): self.frames += 1
c.tick()
pygame.display.flip() flip()
dt = time.time() - t1
gsleep(max(doptim-dt, 0))
def on(self, name): def on(self, name):
''' If used as a decorator, binds a function to an event. ''' ''' If used as a decorator, binds a function to an event. '''
@ -164,6 +153,9 @@ class Canvas(object):
if name in self.events: if name in self.events:
try: try:
self.events[name](self, *a, **ka) self.events[name](self, *a, **ka)
return True
except GreenletExit:
raise
except: except:
log.exception('Error in callback for %r', name) log.exception('Error in callback for %r', name)
@ -178,16 +170,16 @@ class Canvas(object):
def set_pixel(self, x, y, r, g, b, a=255): def set_pixel(self, x, y, r, g, b, a=255):
''' Change the colour of a pixel. If an alpha value is given, the new ''' Change the colour of a pixel. If an alpha value is given, the new
colour is mixed with the old colour accordingly. ''' colour is mixed with the old colour accordingly. '''
if a == 0: return if a == 0:
screen = self.screen return
if a == 0xff: elif a == 0xff:
screen.set_at((x, y), (r,g,b)) self.screen.set_at((x, y), (r,g,b))
elif 0 <= x < self.width and 0 <= y < self.height: elif 0 <= x < self.width and 0 <= y < self.height:
r2, g2, b2, a2 = screen.get_at((x, y)) r2, g2, b2, a2 = self.screen.get_at((x, y))
r = (r2*(0xff-a)+(r*a)) / 0xff r = (r2*(0xff-a)+(r*a)) / 0xff
g = (g2*(0xff-a)+(g*a)) / 0xff g = (g2*(0xff-a)+(g*a)) / 0xff
b = (b2*(0xff-a)+(b*a)) / 0xff b = (b2*(0xff-a)+(b*a)) / 0xff
screen.set_at((x, y), (r,g,b)) self.screen.set_at((x, y), (r,g,b))
def clear(self, r=0, g=0, b=0, a=255): def clear(self, r=0, g=0, b=0, a=255):
''' Fill the entire screen with a solid colour (default: black)''' ''' Fill the entire screen with a solid colour (default: black)'''