diff --git a/builder.py b/builder.py index 990ed750b..29b73f635 100644 --- a/builder.py +++ b/builder.py @@ -5,9 +5,13 @@ import socket import sqlite3 import sys +import os +import time +import boto3 -DEFAULT_HOST = '127.0.0.1' -DEFAULT_PORT = 4080 +QUEUE_URL = os.environ['SQS_CHECKPOINT_QUEUE_URL'] +DEFAULT_HOST = os.environ['CRAFT_HOST'] +DEFAULT_PORT = os.environ['CRAFT_PORT'] EMPTY = 0 GRASS = 1 @@ -45,6 +49,43 @@ (0.5, 0.5, 0.5), ] +sqs=boto3.client('sqs') + +def store_checkpoint(checkpoint): + response=sqs.send_message( + QueueUrl=QUEUE_URL, + DelaySeconds=10, + MessageBody=(checkpoint) + ) + print(response['MessageId']) + sys.stdout.flush() + +def pull_checkpoint(): + response=sqs.receive_message( + QueueUrl=QUEUE_URL, + MaxNumberOfMessages=1, + VisibilityTimeout=0, + WaitTimeSeconds=0 + ) + print('in pull_checkpoint:response:%s'%response) + if 'Messages' in response.keys(): + message=response['Messages'][0] + receipt_handle=message['ReceiptHandle'] + last_checkpoint=message['Body'] + print('in pull_checkpoint:last_checkpoint %s' % last_checkpoint) + sys.stdout.flush() + + sqs.delete_message( + QueueUrl=QUEUE_URL, + ReceiptHandle=receipt_handle + ) + print('in pull_checkpoint:Received and deleted message:%s'%message) + sys.stdout.flush() + else: + print('in pull_checkpoint: no previous checkpoints in queue') + last_checkpoint="1" + return last_checkpoint + def sphere(cx, cy, cz, r, fill=False, fx=False, fy=False, fz=False): result = set() for x in range(cx - r, cx + r + 1): @@ -143,7 +184,7 @@ class Client(object): def __init__(self, host, port): self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn.connect((host, port)) - self.authenticate() + #self.authenticate() def authenticate(self): username, identity_token = get_identity() url = 'https://craft.michaelfogleman.com/api/1/identity' @@ -154,11 +195,27 @@ def authenticate(self): response = requests.post(url, data=payload) if response.status_code == 200 and response.text.isalnum(): access_token = response.text - self.conn.sendall('A,%s,%s\n' % (username, access_token)) + buf=b'' + string='A,%s,%s\n' % (username, access_token) + buf=bytes(string,'utf-8') + self.conn.sendall(buf) + print(buf) + sys.stdout.flush() else: raise Exception('Failed to authenticate.') def set_block(self, x, y, z, w): - self.conn.sendall('B,%d,%d,%d,%d\n' % (x, y, z, w)) + buf=b'' + string='B,%d,%d,%d,%d\n' % (x, y, z, w) + buf=bytes(string,'utf-8') + try: + r=self.conn.sendall(buf) + print('buf={},r={}'.format(buf,r)) + sys.stdout.flush() + time.sleep(1) + except Exception as error: + print('in set_block:error:',error) + sys.stdout.flush() + #self.conn.sendall('B,%d,%d,%d,%d\n' % (x, y, z, w)) def set_blocks(self, blocks, w): key = lambda block: (block[1], block[0], block[2]) for x, y, z in sorted(blocks, key=key): @@ -186,63 +243,83 @@ def get_client(): return client def main(): - client = get_client() - set_block = client.set_block - set_blocks = client.set_blocks - # set_blocks(circle_y(0, 32, 0, 16, True), STONE) - # set_blocks(circle_y(0, 33, 0, 16), BRICK) - # set_blocks(cuboid(-1, 1, 1, 31, -1, 1), CEMENT) - # set_blocks(cuboid(-1024, 1024, 32, 32, -3, 3), STONE) - # set_blocks(cuboid(-3, 3, 32, 32, -1024, 1024), STONE) - # set_blocks(cuboid(-1024, 1024, 33, 33, -3, -3), BRICK) - # set_blocks(cuboid(-1024, 1024, 33, 33, 3, 3), BRICK) - # set_blocks(cuboid(-3, -3, 33, 33, -1024, 1024), BRICK) - # set_blocks(cuboid(3, 3, 33, 33, -1024, 1024), BRICK) - # set_blocks(sphere(0, 32, 0, 16), GLASS) - # for y in range(1, 32): - # set_blocks(circle_y(0, y, 0, 4, True), CEMENT) - # set_blocks(circle_x(16, 33, 0, 3), BRICK) - # set_blocks(circle_x(-16, 33, 0, 3), BRICK) - # set_blocks(circle_z(0, 33, 16, 3), BRICK) - # set_blocks(circle_z(0, 33, -16, 3), BRICK) - # for x in range(0, 1024, 32): - # set_blocks(cuboid(x - 1, x + 1, 31, 32, -1, 1), CEMENT) - # set_blocks(cuboid(-x - 1, -x + 1, 31, 32, -1, 1), CEMENT) - # set_blocks(cuboid(x, x, 1, 32, -1, 1), CEMENT) - # set_blocks(cuboid(-x, -x, 1, 32, -1, 1), CEMENT) - # for z in range(0, 1024, 32): - # set_blocks(cuboid(-1, 1, 31, 32, z - 1, z + 1), CEMENT) - # set_blocks(cuboid(-1, 1, 31, 32, -z - 1, -z + 1), CEMENT) - # set_blocks(cuboid(-1, 1, 1, 32, z, z), CEMENT) - # set_blocks(cuboid(-1, 1, 1, 32, -z, -z), CEMENT) - # for x in range(0, 1024, 8): - # set_block(x, 32, 0, CEMENT) - # set_block(-x, 32, 0, CEMENT) - # for z in range(0, 1024, 8): - # set_block(0, 32, z, CEMENT) - # set_block(0, 32, -z, CEMENT) - # set_blocks(pyramid(32, 32+64-1, 12, 32, 32+64-1), COBBLE) - # outer = circle_y(0, 11, 0, 176 + 3, True) - # inner = circle_y(0, 11, 0, 176 - 3, True) - # set_blocks(outer - inner, STONE) - # a = sphere(-32, 48, -32, 24, True) - # b = sphere(-24, 40, -24, 24, True) - # set_blocks(a - b, PLANK) - # set_blocks(cylinder_x(-64, 64, 32, 0, 8), STONE) - # data = [ - # '...............................', - # '..xxx..xxxx...xxx..xxxxx.xxxxx.', - # '.x...x.x...x.x...x.x.......x...', - # '.x.....xxxx..xxxxx.xxx.....x...', - # '.x...x.x..x..x...x.x.......x...', - # '..xxx..x...x.x...x.x.......x...', - # '...............................', - # ] - # lookup = { - # 'x': STONE, - # '.': PLANK, - # } - # client.bitmap(0, 32, 32, (1, 0, 0), (0, -1, 0), data, lookup) + client = get_client() + set_block = client.set_block + set_blocks = client.set_blocks + #store_checkpoint('1') + last_checkpoint=pull_checkpoint() + print('in main:lastcheckpoint ',last_checkpoint) + match last_checkpoint: + case '1': + set_blocks(circle_y(0, 32, 0, 16, True), STONE) + set_blocks(circle_y(0, 33, 0, 16), BRICK) + set_blocks(cuboid(-1, 1, 1, 31, -1, 1), CEMENT) + set_blocks(cuboid(-1024, 1024, 32, 32, -3, 3), STONE) + set_blocks(cuboid(-3, 3, 32, 32, -1024, 1024), STONE) + set_blocks(cuboid(-1024, 1024, 33, 33, -3, -3), BRICK) + set_blocks(cuboid(-1024, 1024, 33, 33, 3, 3), BRICK) + set_blocks(cuboid(-3, -3, 33, 33, -1024, 1024), BRICK) + set_blocks(cuboid(3, 3, 33, 33, -1024, 1024), BRICK) + set_blocks(sphere(0, 32, 0, 16), GLASS) + store_checkpoint('1') + case '2': + for y in range(1, 32): + set_blocks(circle_y(0, y, 0, 4, True), CEMENT) + set_blocks(circle_x(16, 33, 0, 3), BRICK) + set_blocks(circle_x(-16, 33, 0, 3), BRICK) + set_blocks(circle_z(0, 33, 16, 3), BRICK) + set_blocks(circle_z(0, 33, -16, 3), BRICK) + store_checkpoint('2') + case '3': + for x in range(0, 1024, 32): + set_blocks(cuboid(x - 1, x + 1, 31, 32, -1, 1), CEMENT) + set_blocks(cuboid(-x - 1, -x + 1, 31, 32, -1, 1), CEMENT) + set_blocks(cuboid(x, x, 1, 32, -1, 1), CEMENT) + set_blocks(cuboid(-x, -x, 1, 32, -1, 1), CEMENT) + store_checkpoint('3') + case '4': + for z in range(0, 1024, 32): + set_blocks(cuboid(-1, 1, 31, 32, z - 1, z + 1), CEMENT) + set_blocks(cuboid(-1, 1, 31, 32, -z - 1, -z + 1), CEMENT) + set_blocks(cuboid(-1, 1, 1, 32, z, z), CEMENT) + set_blocks(cuboid(-1, 1, 1, 32, -z, -z), CEMENT) + store_checkpoint('4') + case '5': + for x in range(0, 1024, 8): + set_block(x, 32, 0, CEMENT) + set_block(-x, 32, 0, CEMENT) + store_checkpoint('5') + case '6': + for z in range(0, 1024, 8): + set_block(0, 32, z, CEMENT) + set_block(0, 32, -z, CEMENT) + store_checkpoint('6') + case '7': + set_blocks(pyramid(32, 32+64-1, 12, 32, 32+64-1), COBBLE) + outer = circle_y(0, 11, 0, 176 + 3, True) + inner = circle_y(0, 11, 0, 176 - 3, True) + set_blocks(outer - inner, STONE) + a = sphere(-32, 48, -32, 24, True) + b = sphere(-24, 40, -24, 24, True) + set_blocks(a - b, PLANK) + set_blocks(cylinder_x(-64, 64, 32, 0, 8), STONE) + store_checkpoint('7') + case '8': + data = [ + '...............................', + '..xxx..xxxx...xxx..xxxxx.xxxxx.', + '.x...x.x...x.x...x.x.......x...', + '.x.....xxxx..xxxxx.xxx.....x...', + '.x...x.x..x..x...x.x.......x...', + '..xxx..x...x.x...x.x.......x...', + '...............................', + ] + lookup = { + 'x': STONE, + '.': PLANK, + } + client.bitmap(0, 32, 32, (1, 0, 0), (0, -1, 0), data, lookup) + store_checkpoint('8') if __name__ == '__main__': main() diff --git a/cache.craft.yahav.sa.aws.dev.4080.db-journal b/cache.craft.yahav.sa.aws.dev.4080.db-journal new file mode 100644 index 000000000..0eeda8507 Binary files /dev/null and b/cache.craft.yahav.sa.aws.dev.4080.db-journal differ diff --git a/create_db_schema.sql b/create_db_schema.sql new file mode 100644 index 000000000..e3554ef9f --- /dev/null +++ b/create_db_schema.sql @@ -0,0 +1,64 @@ +create SEQUENCE block_rowid start 1; +create SEQUENCE light_rowid start 1; +create SEQUENCE sign_rowid start 1; +create SEQUENCE block_history_rowid start 1; + +create table block ( + rowid bigint default nextval('public.block_rowid'::regclass) primary key, + updated_at timestamp, + user_id varchar(64) not null, + p int not null, + q int not null, + x int not null, + y int not null, + z int not null, + w int not null +); +alter table block add constraint unique_block_pqxyz unique (p,q,x,y,z); + +create table if not exists light ( + rowid bigint default nextval('public.light_rowid'::regclass) primary key, + p int not null, + q int not null, + x int not null, + y int not null, + z int not null, + w int not null +); +create unique index if not exists light_pqxyz_idx on light (p, q, x, y, z); + +create table if not exists sign ( + rowid bigint default nextval('public.sign_rowid'::regclass) primary key, + p int not null, + q int not null, + x int not null, + y int not null, + z int not null, + face int not null, + text text not null +); +create index if not exists sign_pq_idx on sign (p, q); + +create unique index if not exists sign_xyzface_idx on sign (x, y, z, face); + +create table if not exists block_history ( + rowid bigint default nextval('public.block_history_rowid'::regclass), + created_at timestamp, + user_id varchar(64) not null, + p int not null, + q int not null, + x int not null, + y int not null, + z int not null, + w int not null, + primary key (rowid,created_at) +) partition by range (created_at); + +CREATE SCHEMA partman; +CREATE EXTENSION pg_partman WITH SCHEMA partman; + +SELECT partman.create_parent( p_parent_table => 'public.block_history', + p_control => 'created_at', + p_type => 'native', + p_interval=> 'monthly', + p_premake => 24); diff --git a/deps/glfw/src/glfw3Config.cmake b/deps/glfw/src/glfw3Config.cmake new file mode 100644 index 000000000..f41a722a2 --- /dev/null +++ b/deps/glfw/src/glfw3Config.cmake @@ -0,0 +1,29 @@ +# - Config file for the glfw3 package +# It defines the following variables +# GLFW3_INCLUDE_DIR, the path where GLFW headers are located +# GLFW3_LIBRARY_DIR, folder in which the GLFW library is located +# GLFW3_LIBRARY, library to link against to use GLFW + +set(GLFW3_VERSION "3.1.2") + + +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() ####### +####### Any changes to this file will be overwritten by the next CMake run #### +####### The input file was glfw3Config.cmake.in ######## + +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) + +macro(set_and_check _var _file) + set(${_var} "${_file}") + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") + endif() +endmacro() + +#################################################################################### + +set_and_check(GLFW3_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") +set_and_check(GLFW3_LIBRARY_DIR "${PACKAGE_PREFIX_DIR}/lib") + +find_library(GLFW3_LIBRARY "glfw3" HINTS ${GLFW3_LIBRARY_DIR}) + diff --git a/deps/glfw/src/glfw3ConfigVersion.cmake b/deps/glfw/src/glfw3ConfigVersion.cmake new file mode 100644 index 000000000..d73cffc15 --- /dev/null +++ b/deps/glfw/src/glfw3ConfigVersion.cmake @@ -0,0 +1,70 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version, +# but only if the requested major version is the same as the current one. +# The variable CVF_VERSION must be set before calling configure_file(). + + +set(PACKAGE_VERSION "3.1.2") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + + if("3.1.2" MATCHES "^([0-9]+)\\.") + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + else() + set(CVF_VERSION_MAJOR "3.1.2") + endif() + + if(PACKAGE_FIND_VERSION_RANGE) + # both endpoints of the range must have the expected major version + math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1") + if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + else() + if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed project requested no architecture check, don't perform the check +if("FALSE") + return() +endif() + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/deps/glfw/src/glfw_config.h b/deps/glfw/src/glfw_config.h new file mode 100644 index 000000000..fc58e1781 --- /dev/null +++ b/deps/glfw/src/glfw_config.h @@ -0,0 +1,81 @@ +//======================================================================== +// GLFW 3.1 - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2010 Camilla Berglund +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +// As glfw_config.h.in, this file is used by CMake to produce the +// glfw_config.h configuration header file. If you are adding a feature +// requiring conditional compilation, this is where to add the macro. +//======================================================================== +// As glfw_config.h, this file defines compile-time option macros for a +// specific platform and development environment. If you are using the +// GLFW CMake files, modify glfw_config.h.in instead of this file. If you +// are using your own build system, make this file define the appropriate +// macros in whatever way is suitable. +//======================================================================== + +// Define this to 1 if building GLFW for X11 +/* #undef _GLFW_X11 */ +// Define this to 1 if building GLFW for Win32 +/* #undef _GLFW_WIN32 */ +// Define this to 1 if building GLFW for Cocoa +#define _GLFW_COCOA +// Define this to 1 if building GLFW for Wayland +/* #undef _GLFW_WAYLAND */ +// Define this to 1 if building GLFW for Mir +/* #undef _GLFW_MIR */ + +// Define this to 1 if building GLFW for EGL +/* #undef _GLFW_EGL */ +// Define this to 1 if building GLFW for GLX +/* #undef _GLFW_GLX */ +// Define this to 1 if building GLFW for WGL +/* #undef _GLFW_WGL */ +// Define this to 1 if building GLFW for NSGL +#define _GLFW_NSGL + +// Define this to 1 if building as a shared library / dynamic library / DLL +/* #undef _GLFW_BUILD_DLL */ + +// Define this to 1 to force use of high-performance GPU on hybrid systems +/* #undef _GLFW_USE_HYBRID_HPG */ + +// Define this to 1 if the XInput X11 extension is available +/* #undef _GLFW_HAS_XINPUT */ +// Define this to 1 if the Xxf86vm X11 extension is available +/* #undef _GLFW_HAS_XF86VM */ + +// Define this to 1 if glfwInit should change the current directory +#define _GLFW_USE_CHDIR +// Define this to 1 if glfwCreateWindow should populate the menu bar +#define _GLFW_USE_MENUBAR +// Define this to 1 if windows should use full resolution on Retina displays +#define _GLFW_USE_RETINA + +// Define this to 1 if using OpenGL as the client library +#define _GLFW_USE_OPENGL +// Define this to 1 if using OpenGL ES 1.1 as the client library +/* #undef _GLFW_USE_GLESV1 */ +// Define this to 1 if using OpenGL ES 2.0 as the client library +/* #undef _GLFW_USE_GLESV2 */ + diff --git a/deps/glfw/tests/cursor b/deps/glfw/tests/cursor new file mode 100755 index 000000000..feb3741e4 Binary files /dev/null and b/deps/glfw/tests/cursor differ diff --git a/deps/glfw/tests/monitors b/deps/glfw/tests/monitors new file mode 100755 index 000000000..43be08af7 Binary files /dev/null and b/deps/glfw/tests/monitors differ diff --git a/deps/glfw/tests/msaa b/deps/glfw/tests/msaa new file mode 100755 index 000000000..947420263 Binary files /dev/null and b/deps/glfw/tests/msaa differ diff --git a/server.py b/server.py index df8e596d9..016aff7de 100644 --- a/server.py +++ b/server.py @@ -1,19 +1,37 @@ from math import floor from world import World -import Queue -import SocketServer +import queue +import socketserver import datetime import random import re import requests -import sqlite3 import sys import threading import time import traceback +import os +import psycopg2 +import signal +import datetime +from datetime import datetime, timezone +from datetime import datetime as dt + +cmd = 'rm -rf /tmp/healthy' +user=os.environ['PGUSER'] +password=os.environ['PGPASSWORD'] +host=os.environ['PGHOST'] +database=os.environ['PGDATABASE'] +dbport=os.environ['PGPORT'] +pod_name=os.environ['MY_POD_NAME'] +node_name=os.environ['MY_NODE_NAME'] DEFAULT_HOST = '0.0.0.0' -DEFAULT_PORT = 4080 +DEFAULT_PORT = int(os.environ['SERVERPORT']) +IS_AGONES=os.environ['IS_AGONES'] + +if IS_AGONES == 'True': + AGONES_SDK_HTTP_PORT=os.environ['AGONES_SDK_HTTP_PORT'] DB_PATH = 'craft.db' LOG_PATH = 'log.txt' @@ -22,13 +40,14 @@ BUFFER_SIZE = 4096 COMMIT_INTERVAL = 5 -AUTH_REQUIRED = True -AUTH_URL = 'https://craft.michaelfogleman.com/api/1/access' +AUTH_REQUIRED = os.environ['USE_AUTH'] +AUTH_URL = os.environ['AUTH_SRV'] DAY_LENGTH = 600 -SPAWN_POINT = (0, 0, 0, 0, 0) +#SPAWN_POINT = (0, 0, 0, 0, 0) +SPAWN_POINT = tuple(os.environ['START_POINT']) RATE_LIMIT = False -RECORD_HISTORY = False +RECORD_HISTORY =os.environ['RECORD_HISTORY'] INDESTRUCTIBLE_ITEMS = set([16]) ALLOWED_ITEMS = set([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, @@ -57,17 +76,65 @@ pass def log(*args): - now = datetime.datetime.utcnow() + now = dt.now() line = ' '.join(map(str, (now,) + args)) - print line + print(line) with open(LOG_PATH, 'a') as fp: fp.write('%s\n' % line) + sys.stdout.flush() + +def sig_handler(signum,frame): + log('Signal hanlder called with signal',signum) + log('execute ',cmd) + os.system(cmd) + model.send_talk("Game server maintenance is pending - pls reconnect") + model.send_talk("Don't worry, your universe is saved with us") + model.send_talk('Removing the server from load balancer %s'%(cmd)) + +def pg_read(sql,param): + try: + connection = psycopg2.connect(user=user, + password=password, + host=host, + port=dbport, + database=database) + cursor = connection.cursor() + cursor.execute(sql,param) + rows = cursor.fetchall() + #log('in pg_read:','sql=',sql,'param=',param,'rows=',rows) + return rows + except (Exception, psycopg2.Error) as error: + log('Failed to select:',error,' sql:',sql,' param:',param) + finally: + if connection: + cursor.close() + connection.close() + +def pg_write(sql,param): + try: + connection = psycopg2.connect(user=user, + password=password, + host=host, + port=dbport, + database=database) + cursor = connection.cursor() + cursor.execute(sql,param) + connection.commit() + count = cursor.rowcount + #log('in pg_write:','sql=',sql,'param=',param,'count=',count) + except (Exception, psycopg2.Error) as error: + log('Failed to insert/update:',error,' sql:',sql,' param:',param) + traceback.print_exc() + finally: + if connection: + cursor.close() + connection.close() def chunked(x): return int(floor(round(x) / CHUNK_SIZE)) def packet(*args): - return '%s\n' % ','.join(map(str, args)) + return ('%s\n' % ','.join(map(str, args))).encode("UTF-8") class RateLimiter(object): def __init__(self, rate, per): @@ -90,11 +157,11 @@ def tick(self): self.allowance -= 1 return False # okay -class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer): +class Server(socketserver.ThreadingMixIn, socketserver.TCPServer): allow_reuse_address = True daemon_threads = True -class Handler(SocketServer.BaseRequestHandler): +class Handler(socketserver.BaseRequestHandler): def setup(self): self.position_limiter = RateLimiter(100, 5) self.limiter = RateLimiter(1000, 10) @@ -102,67 +169,93 @@ def setup(self): self.client_id = None self.user_id = None self.nick = None - self.queue = Queue.Queue() + self.queue = queue.Queue() self.running = True self.start() + if IS_AGONES == 'True': + self.start_agones_health() def handle(self): model = self.server.model model.enqueue(model.on_connect, self) try: - buf = [] + buf = b'' while True: data = self.request.recv(BUFFER_SIZE) if not data: break - buf.extend(data.replace('\r\n', '\n')) - while '\n' in buf: - index = buf.index('\n') - line = ''.join(buf[:index]) + buf += data.replace(b'\r\n', b'\n') + while b'\n' in buf: + index = buf.index(b'\n') + line = buf[:index] buf = buf[index + 1:] - if not line: - continue - if line[0] == POSITION: + command = line.decode("utf-8") + if command[0] == POSITION: if self.position_limiter.tick(): - log('RATE', self.client_id) self.stop() return else: if self.limiter.tick(): - log('RATE', self.client_id) self.stop() return - model.enqueue(model.on_data, self, line) + model.enqueue(model.on_data, self, command) finally: model.enqueue(model.on_disconnect, self) + def agones_shutdown(self): + try: + headers={'Content-Type':'application/json'} + url='http://localhost:'+AGONES_SDK_HTTP_PORT+'/shutdown' + r=requests.post(url,headers=headers,json={}) + log('in Handler:run:response-agones:url:',url, ' response.status_code:',r.status_code,' response.headers:',r.headers) + except Exception as error: + log('agones_shutdown:',error) def finish(self): + #if IS_AGONES == 'True': + # self.agones_shutdown() self.running = False def stop(self): self.request.close() def start(self): thread = threading.Thread(target=self.run) - thread.setDaemon(True) + thread.setDaemon=True + thread.start() + def start_agones_health(self): + thread = threading.Thread(target=self.agones_health) + thread.setDaemon=True thread.start() + def agones_health(self): + while self.running: + try: + headers={'Content-Type':'application/json'} + url='http://localhost:'+AGONES_SDK_HTTP_PORT+'/health' + r=requests.post(url,headers=headers,json={}) + #log('in Handler:run:response-agones:url:',url, ' response.status_code:',r.status_code,' response.headers:',r.headers) + time.sleep(10) + except Exception as error: + log('agones_health:error',error) + def run(self): while self.running: try: - buf = [] + buf = b'' try: - buf.append(self.queue.get(timeout=5)) + buf += self.queue.get(timeout=5) try: while True: - buf.append(self.queue.get(False)) - except Queue.Empty: + buf += self.queue.get_nowait() + except queue.Empty: pass - except Queue.Empty: + #log('in Handler:run:buf:',buf) + except queue.Empty: continue - data = ''.join(buf) - self.request.sendall(data) - except Exception: + self.request.sendall(buf) + except Exception as error: self.request.close() - raise + #raise + def send_raw(self, data): if data: self.queue.put(data) + def send(self, *args): self.send_raw(packet(*args)) @@ -170,7 +263,7 @@ class Model(object): def __init__(self, seed): self.world = World(seed) self.clients = [] - self.queue = Queue.Queue() + self.queue = queue.Queue() self.commands = { AUTHENTICATE: self.on_authenticate, CHUNK: self.on_chunk, @@ -191,101 +284,66 @@ def __init__(self, seed): ] def start(self): thread = threading.Thread(target=self.run) - thread.setDaemon(True) + thread.daemon= True thread.start() def run(self): - self.connection = sqlite3.connect(DB_PATH) - self.create_tables() - self.commit() while True: try: - if time.time() - self.last_commit > COMMIT_INTERVAL: - self.commit() self.dequeue() except Exception: traceback.print_exc() + def enqueue(self, func, *args, **kwargs): - self.queue.put((func, args, kwargs)) + try: + self.queue.put((func, args, kwargs)) + except Exception as error: + log('in enqueue:Exception:',error) + traceback.print_exc() + def dequeue(self): try: func, args, kwargs = self.queue.get(timeout=5) func(*args, **kwargs) - except Queue.Empty: + except queue.Empty: pass - def execute(self, *args, **kwargs): - return self.connection.execute(*args, **kwargs) - def commit(self): - self.last_commit = time.time() - self.connection.commit() - def create_tables(self): - queries = [ - 'create table if not exists block (' - ' p int not null,' - ' q int not null,' - ' x int not null,' - ' y int not null,' - ' z int not null,' - ' w int not null' - ');', - 'create unique index if not exists block_pqxyz_idx on ' - ' block (p, q, x, y, z);', - 'create table if not exists light (' - ' p int not null,' - ' q int not null,' - ' x int not null,' - ' y int not null,' - ' z int not null,' - ' w int not null' - ');', - 'create unique index if not exists light_pqxyz_idx on ' - ' light (p, q, x, y, z);', - 'create table if not exists sign (' - ' p int not null,' - ' q int not null,' - ' x int not null,' - ' y int not null,' - ' z int not null,' - ' face int not null,' - ' text text not null' - ');', - 'create index if not exists sign_pq_idx on sign (p, q);', - 'create unique index if not exists sign_xyzface_idx on ' - ' sign (x, y, z, face);', - 'create table if not exists block_history (' - ' timestamp real not null,' - ' user_id int not null,' - ' x int not null,' - ' y int not null,' - ' z int not null,' - ' w int not null' - ');', - ] - for query in queries: - self.execute(query) + def get_default_block(self, x, y, z): p, q = chunked(x), chunked(z) chunk = self.world.get_chunk(p, q) return chunk.get((x, y, z), 0) + def get_block(self, x, y, z): - query = ( - 'select w from block where ' - 'p = :p and q = :q and x = :x and y = :y and z = :z;' - ) p, q = chunked(x), chunked(z) - rows = list(self.execute(query, dict(p=p, q=q, x=x, y=y, z=z))) + sql = """select w from block where p = %s and q = %s and x = %s and y = %s and z = %s;""" + params = [p,q,x,y,z] + rows = list(pg_read(sql,params)) if rows: return rows[0][0] return self.get_default_block(x, y, z) + def next_client_id(self): result = 1 client_ids = set(x.client_id for x in self.clients) while result in client_ids: result += 1 return result + + def agones_player(self,client_nick,action): + try: + headers={'Content-Type':'application/json'} + url='http://localhost:'+AGONES_SDK_HTTP_PORT+'/alpha/player/'+action + payload={'playerID':client_nick} + r=requests.post(url,headers=headers,json={}) + log('in Handler:run:response-agones:url:',url, ' response.status_code:',r.status_code,' response.headers:',r.headers) + except Exception as error: + log('agones_player_',action,':error',error) + def on_connect(self, client): client.client_id = self.next_client_id() client.nick = 'guest%d' % client.client_id - log('CONN', client.client_id, *client.client_address) + #log('on_connect:', client.client_id, *client.client_address) + #if IS_AGONES == 'True': + # self.agones_player(client.nick,'connect') client.position = SPAWN_POINT self.clients.append(client) client.send(YOU, client.client_id, *client.position) @@ -296,18 +354,21 @@ def on_connect(self, client): self.send_positions(client) self.send_nick(client) self.send_nicks(client) + def on_data(self, client, data): - #log('RECV', client.client_id, data) args = data.split(',') command, args = args[0], args[1:] if command in self.commands: func = self.commands[command] func(client, *args) + def on_disconnect(self, client): - log('DISC', client.client_id, *client.client_address) + #log('on_disconnect:',self.next_client_id()) + #if IS_AGONES == 'True': + # self.agones_player(client.nick,'disconnect') self.clients.remove(client) self.send_disconnect(client) - self.send_talk('%s has disconnected from the server.' % client.nick) + def on_version(self, client, version): if client.version is not None: return @@ -317,70 +378,79 @@ def on_version(self, client, version): return client.version = version # TODO: client.start() here + def on_authenticate(self, client, username, access_token): + log('on_authenticate:',' client:',client,' username:',username,' access_token:',access_token) user_id = None if username and access_token: payload = { 'username': username, 'access_token': access_token, } - response = requests.post(AUTH_URL, data=payload) - if response.status_code == 200 and response.text.isdigit(): - user_id = int(response.text) - client.user_id = user_id + log('on_authenticate:payload',payload) + response = requests.post(AUTH_URL, json=payload) + log('on_authenticate:response.status_code',response.status_code) + #if response.status_code == 200 and response.text.isdigit(): + if response.status_code == 200: + user_id = username if user_id is None: - client.nick = 'guest%d' % client.client_id - client.send(TALK, 'Visit craft.michaelfogleman.com to register!') + client.nick = 'guest%d' % client.client_id + client.user_id='guest%d' % client.client_id + client.send(TALK, 'Visit http://craft.auth.yahav.sa.aws.dev/auth/adduser/?username=myuser to register!') else: - client.nick = username - self.send_nick(client) - # TODO: has left message if was already authenticated + client.nick = username + client.user_id = user_id + self.send_nick(client) + log('on_authenticate:client.nick:',client.nick) + client.send(TALK, 'Current pod is '+pod_name) + client.send(TALK, 'Current node is '+node_name) self.send_talk('%s has joined the game.' % client.nick) + def on_chunk(self, client, p, q, key=0): - packets = [] + packets = b'' p, q, key = map(int, (p, q, key)) - query = ( - 'select rowid, x, y, z, w from block where ' - 'p = :p and q = :q and rowid > :key;' - ) - rows = self.execute(query, dict(p=p, q=q, key=key)) + query="""select rowid, x, y, z, w from block where p = %s and q = %s and rowid > %s;""" + params=[p,q,key] + rows=pg_read(query,params) max_rowid = 0 blocks = 0 - for rowid, x, y, z, w in rows: + if rows is not None: + for rowid, x, y, z, w in rows: blocks += 1 - packets.append(packet(BLOCK, p, q, x, y, z, w)) + packets += packet(BLOCK, p, q, x, y, z, w) max_rowid = max(max_rowid, rowid) - query = ( - 'select x, y, z, w from light where ' - 'p = :p and q = :q;' - ) - rows = self.execute(query, dict(p=p, q=q)) + query="""select x, y, z, w from light where p=%s and q=%s;""" + params=[p,q] + rows=pg_read(query,params) lights = 0 for x, y, z, w in rows: lights += 1 - packets.append(packet(LIGHT, p, q, x, y, z, w)) - query = ( - 'select x, y, z, face, text from sign where ' - 'p = :p and q = :q;' - ) - rows = self.execute(query, dict(p=p, q=q)) + packets += packet(LIGHT, p, q, x, y, z, w) + query="""select x, y, z, face, text from sign where p = %s and q = %s;""" + params=[p,q] + rows=pg_read(query,params) signs = 0 for x, y, z, face, text in rows: signs += 1 - packets.append(packet(SIGN, p, q, x, y, z, face, text)) + packets += packet(SIGN, p, q, x, y, z, face, text) if blocks: - packets.append(packet(KEY, p, q, max_rowid)) + packets += packet(KEY, p, q, max_rowid) if blocks or lights or signs: - packets.append(packet(REDRAW, p, q)) - packets.append(packet(CHUNK, p, q)) - client.send_raw(''.join(packets)) + packets += packet(REDRAW, p, q) + packets += packet(CHUNK, p, q) + client.send_raw(packets) + def on_block(self, client, x, y, z, w): + #log('in on_block:',x,y,z,w) x, y, z, w = map(int, (x, y, z, w)) p, q = chunked(x), chunked(z) previous = self.get_block(x, y, z) message = None + #TODO: remove after builder is done + #if client.user_id is None: + # client.user_id = "builder" if AUTH_REQUIRED and client.user_id is None: - message = 'Only logged in users are allowed to build.' + message = 'in on_block - Only logged in users are allowed to build.' elif y <= 0 or y > 255: message = 'Invalid block coordinates.' elif w not in ALLOWED_ITEMS: @@ -396,18 +466,14 @@ def on_block(self, client, x, y, z, w): client.send(REDRAW, p, q) client.send(TALK, message) return - query = ( - 'insert into block_history (timestamp, user_id, x, y, z, w) ' - 'values (:timestamp, :user_id, :x, :y, :z, :w);' - ) + now = dt.now() if RECORD_HISTORY: - self.execute(query, dict(timestamp=time.time(), - user_id=client.user_id, x=x, y=y, z=z, w=w)) - query = ( - 'insert or replace into block (p, q, x, y, z, w) ' - 'values (:p, :q, :x, :y, :z, :w);' - ) - self.execute(query, dict(p=p, q=q, x=x, y=y, z=z, w=w)) + sql = """insert into block_history (created_at,user_id,p,q,x,y,z,w) values (%s,%s,%s,%s,%s,%s,%s,%s)""" + params=[now,client.user_id,p,q,x,y,z,w] + response=pg_write(sql,params) + sql = """insert into block (updated_at,user_id,p,q,x,y,z,w) values (%s,%s,%s,%s,%s,%s,%s,%s) on conflict on constraint unique_block_pqxyz do UPDATE SET w =%s,updated_at=%s""" + params=[now,client.user_id,p,q,x,y,z,w,w,now] + response=pg_write(sql,params) self.send_block(client, p, q, x, y, z, w) for dx in range(-1, 2): for dz in range(-1, 2): @@ -418,26 +484,26 @@ def on_block(self, client, x, y, z, w): if dz and chunked(z + dz) == q: continue np, nq = p + dx, q + dz - self.execute(query, dict(p=np, q=nq, x=x, y=y, z=z, w=-w)) + #params=[now,np,nq,x,y,z,-w] + params=[now,client.user_id,np,nq,x,y,z,-w,-w,now] + response=pg_write(sql,params) self.send_block(client, np, nq, x, y, z, -w) if w == 0: - query = ( - 'delete from sign where ' - 'x = :x and y = :y and z = :z;' - ) - self.execute(query, dict(x=x, y=y, z=z)) - query = ( - 'update light set w = 0 where ' - 'x = :x and y = :y and z = :z;' - ) - self.execute(query, dict(x=x, y=y, z=z)) + sql = """delete from sign where x = %s and y = %s and z = %s""" + params=[x,y,z] + response=pg_write(sql,params) + sql = """update light set w = 0 where x = %s and y = %s and z = %s""" + params=[x,y,z] + response=pg_write(sql,params) + def on_light(self, client, x, y, z, w): x, y, z, w = map(int, (x, y, z, w)) p, q = chunked(x), chunked(z) block = self.get_block(x, y, z) message = None if AUTH_REQUIRED and client.user_id is None: - message = 'Only logged in users are allowed to build.' + #message = 'Only logged in users are allowed to build.' + message = 'in on_block - Only logged in users are allowed to build.' elif block == 0: message = 'Lights must be placed on a block.' elif w < 0 or w > 15: @@ -447,15 +513,15 @@ def on_light(self, client, x, y, z, w): client.send(REDRAW, p, q) client.send(TALK, message) return - query = ( - 'insert or replace into light (p, q, x, y, z, w) ' - 'values (:p, :q, :x, :y, :z, :w);' - ) - self.execute(query, dict(p=p, q=q, x=x, y=y, z=z, w=w)) + sql = """insert or replace into light (p, q, x, y, z, w) values (%s,%s,%s,%s,%s,%s)""" + params=[p,q,x,y,z,w] + response=pg_write(sql,params) self.send_light(client, p, q, x, y, z, w) + def on_sign(self, client, x, y, z, face, *args): if AUTH_REQUIRED and client.user_id is None: - client.send(TALK, 'Only logged in users are allowed to build.') + # client.send(TALK, 'Only logged in users are allowed to build.') + client.send(TALK, 'in on_block - Only logged in users are allowed to build.') return text = ','.join(args) x, y, z, face = map(int, (x, y, z, face)) @@ -467,23 +533,20 @@ def on_sign(self, client, x, y, z, face, *args): return p, q = chunked(x), chunked(z) if text: - query = ( - 'insert or replace into sign (p, q, x, y, z, face, text) ' - 'values (:p, :q, :x, :y, :z, :face, :text);' - ) - self.execute(query, - dict(p=p, q=q, x=x, y=y, z=z, face=face, text=text)) + sql = """insert or replace into sign (p, q, x, y, z, face, text) values (%s,%s,%s,%s,%s,%s,%s)""" + params[p,q,x,y,z,face,text] + response=pg_write(sql,params) else: - query = ( - 'delete from sign where ' - 'x = :x and y = :y and z = :z and face = :face;' - ) - self.execute(query, dict(x=x, y=y, z=z, face=face)) + sql = """delete from sign where x = %s and y = %s and z = %s and face = %s""" + params=[x,y,z,face] + response=pg_write(sql,params) self.send_sign(client, p, q, x, y, z, face, text) + def on_position(self, client, x, y, z, rx, ry): x, y, z, rx, ry = map(float, (x, y, z, rx, ry)) client.position = (x, y, z, rx, ry) self.send_position(client) + def on_talk(self, client, *args): text = ','.join(args) if text.startswith('/'): @@ -505,6 +568,7 @@ def on_talk(self, client, *args): client.send(TALK, 'Unrecognized nick: "%s"' % nick) else: self.send_talk('%s> %s' % (client.nick, text)) + def on_nick(self, client, nick=None): if AUTH_REQUIRED: client.send(TALK, 'You cannot change your nick on this server.') @@ -515,10 +579,12 @@ def on_nick(self, client, nick=None): self.send_talk('%s is now known as %s' % (client.nick, nick)) client.nick = nick self.send_nick(client) + def on_spawn(self, client): client.position = SPAWN_POINT client.send(YOU, client.client_id, *client.position) self.send_position(client) + def on_goto(self, client, nick=None): if nick is None: clients = [x for x in self.clients if x != client] @@ -530,6 +596,7 @@ def on_goto(self, client, nick=None): client.position = other.position client.send(YOU, client.client_id, *client.position) self.send_position(client) + def on_pq(self, client, p, q): p, q = map(int, (p, q)) if abs(p) > 1000 or abs(q) > 1000: @@ -537,6 +604,7 @@ def on_pq(self, client, p, q): client.position = (p * CHUNK_SIZE, 0, q * CHUNK_SIZE, 0, 0) client.send(YOU, client.client_id, *client.position) self.send_position(client) + def on_help(self, client, topic=None): if topic is None: client.send(TALK, 'Type "t" to chat. Type "/" to type commands:') @@ -626,47 +694,31 @@ def send_talk(self, text): for client in self.clients: client.send(TALK, text) -def cleanup(): - world = World(None) - conn = sqlite3.connect(DB_PATH) - query = 'select x, y, z from block order by rowid desc limit 1;' - last = list(conn.execute(query))[0] - query = 'select distinct p, q from block;' - chunks = list(conn.execute(query)) - count = 0 - total = 0 - delete_query = 'delete from block where x = %d and y = %d and z = %d;' - print 'begin;' - for p, q in chunks: - chunk = world.create_chunk(p, q) - query = 'select x, y, z, w from block where p = :p and q = :q;' - rows = conn.execute(query, {'p': p, 'q': q}) - for x, y, z, w in rows: - if chunked(x) != p or chunked(z) != q: - continue - total += 1 - if (x, y, z) == last: - continue - original = chunk.get((x, y, z), 0) - if w == original or original in INDESTRUCTIBLE_ITEMS: - count += 1 - print delete_query % (x, y, z) - conn.close() - print 'commit;' - print >> sys.stderr, '%d of %d blocks will be cleaned up' % (count, total) +def agones_ready(): + try: + headers={'Content-Type':'application/json'} + url='http://localhost:'+AGONES_SDK_HTTP_PORT+'/ready' + payload={} + r=requests.post(url,headers=headers,json={}) + #log('in Handler:run:response-agones:url:',url, ' response.status_code:',r.status_code,' response.headers:',r.headers) + except Exception as error: + log('agones_ready:',error) + +model = Model(None) +model.start() def main(): - if len(sys.argv) == 2 and sys.argv[1] == 'cleanup': - cleanup() - return + #log("main","AUTH_REQUIRED",AUTH_REQUIRED) + #log("main","AUTH_URL",AUTH_URL) host, port = DEFAULT_HOST, DEFAULT_PORT if len(sys.argv) > 1: host = sys.argv[1] if len(sys.argv) > 2: port = int(sys.argv[2]) log('SERV', host, port) - model = Model(None) - model.start() + if IS_AGONES == 'True': + agones_ready() + signal.signal(signal.SIGTERM,sig_handler) server = Server((host, port), Handler) server.model = model server.serve_forever() diff --git a/src/auth.c b/src/auth.c index 4ff781580..d9357951f 100644 --- a/src/auth.c +++ b/src/auth.c @@ -21,7 +21,9 @@ size_t write_function(char *data, size_t size, size_t count, void *arg) { int get_access_token( char *result, int length, char *username, char *identity_token) { - static char url[] = "https://craft.michaelfogleman.com/api/1/identity"; + printf("auth.c/get_access_token:username=%s,identity_token=%s\n",username,identity_token); + //static char url[] = "https://craft.michaelfogleman.com/api/1/identity"; + static char url[] = "http://craft.auth.yahav.sa.aws.dev/auth/identity/"; strncpy(result, "", length); CURL *curl = curl_easy_init(); if (curl) { @@ -30,6 +32,7 @@ int get_access_token( long http_code = 0; snprintf(post, MAX_POST_LENGTH, "username=%s&identity_token=%s", username, identity_token); + printf("auth.c/get_access_token2:username=%s,identity_token=%s\n",username,identity_token); #ifdef _WIN32 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); #endif