Now with even more pixels!
This commit is contained in:
parent
ab69b8f86f
commit
5911691434
2 changed files with 88 additions and 75 deletions
53
brain.py
53
brain.py
|
|
@ -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
|
|
||||||
|
|
|
||||||
102
pixelflut.py
102
pixelflut.py
|
|
@ -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:
|
|
||||||
sendall(self.sendbuffer.popleft())
|
|
||||||
line = readline().strip()
|
|
||||||
if not line:
|
if not line:
|
||||||
break
|
break
|
||||||
arguments = line.split()
|
arguments = line.split()
|
||||||
command = arguments.pop(0)
|
command = arguments.pop(0)
|
||||||
try:
|
if not self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments):
|
||||||
self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments)
|
self.disconnect()
|
||||||
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)'''
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue