Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve loading speed and output #36

Merged
merged 4 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions gamescript/battle.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import glob
import os
import sys
import time
from datetime import datetime, timedelta
from random import randint

Expand All @@ -10,12 +11,12 @@
from gamescript import camera, weather, battleui, subunit, datasprite, damagesprite, effectsprite, ai
from gamescript.common import utility

direction_list = datasprite.direction_list

from pygame.locals import *

from pathlib import Path

direction_list = datasprite.direction_list


load_image = utility.load_image
load_images = utility.load_images
csv_read = utility.csv_read
Expand All @@ -27,6 +28,24 @@
script_dir = os.path.split(os.path.abspath(__file__))[0] + "/"


# ----
# perhaps this can be in its own file?

load_timer = None


def set_start_load(what):
globals()['load_timer'] = time.time()
return "Loading {0}... ".format(what)


def set_done_load():
duration = time.time() - globals()['load_timer']
return " DONE ({0}s)\n".format(duration)

# ---


class Battle:
empty_method = utility.empty_method

Expand Down Expand Up @@ -299,8 +318,15 @@ def __init__(self, main):

def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map_selected,
map_source, char_selected, map_info, camp_pos):
"""Setup stuff when start new battle"""

for message in self.inner_prepare_new_game(ruleset, ruleset_folder, team_selected, map_type, map_selected,
map_source, char_selected, map_info, camp_pos):
print(message, end="")

def inner_prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map_selected,
map_source, char_selected, map_info, camp_pos):
self.language = self.main.language
"""Setup stuff when start new battle"""

self.ruleset = ruleset # current ruleset used
self.ruleset_folder = ruleset_folder # the folder of rulseset used
Expand Down Expand Up @@ -328,6 +354,7 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
self.team_colour = self.main.team_colour

# Load weather schedule
yield set_start_load("weather")
try:
self.weather_event = csv_read(self.main_dir, "weather.csv",
("data", "ruleset", self.ruleset_folder, "map", map_type, self.map_selected,
Expand All @@ -340,8 +367,10 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
new_time = timedelta(hours=new_time.hour, minutes=new_time.minute, seconds=new_time.second)
self.weather_event = ((4, new_time, 0, 0),) # default weather light sunny all day
self.weather_playing = self.weather_event[0][1] # used as the reference for map starting time
yield set_done_load()

# Random music played from list
yield set_start_load("music")
if pygame.mixer:
self.SONG_END = pygame.USEREVENT + 1
self.music_list = glob.glob(os.path.join(self.main_dir, "data", "sound", "music", "*.ogg"))
Expand All @@ -367,7 +396,9 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
except: # any reading error will play random custom music instead
self.music_schedule = [self.weather_playing]
self.music_event = [] # TODO change later when has custom playlist
yield set_done_load()

yield set_start_load("map events")
try: # get new map event for event log
map_event = csv_read(self.main_dir, "eventlog_" + self.language + ".csv",
("data", "ruleset", self.ruleset_folder, "map", map_type,
Expand All @@ -391,7 +422,9 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
self.event_list.append(event)

self.time_number.start_setup(self.weather_playing)
yield set_done_load()

yield set_start_load("images")
images = load_images(self.main_dir,
subfolder=("ruleset", self.ruleset_folder, "map", map_type, self.map_selected))
self.battle_map_base.draw_image(images["base"])
Expand All @@ -402,8 +435,13 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
place_name_map = images["place_name"]
else:
place_name_map = None
yield set_done_load()

yield set_start_load("draw map")
self.battle_map.draw_image(self.battle_map_base, self.battle_map_feature, place_name_map, self.camp_pos, self)
yield set_done_load()

yield set_start_load("common setup")
self.map_corner = (
len(self.battle_map_base.map_array[0]),
len(self.battle_map_base.map_array)) # get map size that troop can move
Expand Down Expand Up @@ -434,11 +472,14 @@ def prepare_new_game(self, ruleset, ruleset_folder, team_selected, map_type, map
self.visible_subunit_list = {key: {} for key in self.all_team_subunit.keys()}

self.battle_scale_ui.change_fight_scale(self.battle_scale)
yield set_done_load()

yield set_start_load("sprites")
subunit_to_make = tuple(set([this_subunit.troop_id for this_subunit in self.subunit_updater]))
who_todo = {key: value for key, value in self.troop_data.troop_list.items() if key in subunit_to_make}
who_todo |= {key: value for key, value in self.leader_data.leader_list.items() if key in subunit_to_make}
self.subunit_animation_pool, self.status_animation_pool = self.main.create_troop_sprite_pool(who_todo)
yield set_done_load()

def run_game(self):
# Create Starting Values
Expand Down
36 changes: 36 additions & 0 deletions gamescript/common/game/create_troop_sprite_pool.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
import os
import hashlib
import pickle
import pygame
import threading
from random import choice

import pygame
from PIL import Image

from gamescript.common.game import create_troop_sprite
from gamescript.create_troop_sprite_pool_methods import recursive_cast_surface_to_pickleable_surface
from gamescript.create_troop_sprite_pool_methods import recursive_cast_pickleable_surface_to_surface


def create_troop_sprite_pool(self, who_todo, preview=False, specific_preview=None, max_preview_size=200):

stringified_arguments = "".join(sorted(map(str, who_todo.keys())))+"p"+str(max_preview_size)
md5 = hashlib.md5(stringified_arguments.encode()).hexdigest()

cache_file_path = os.path.join(self.main_dir, 'cache_{0}.pickle'.format(md5))

if not os.path.isfile(cache_file_path):
pool = inner_create_troop_sprite_pool(self, who_todo, preview, specific_preview, max_preview_size)

assert type(pool) == tuple
assert len(pool) == 2
assert type(pool[0]) == dict
assert type(pool[1]) == dict

recursive_cast_surface_to_pickleable_surface(pool[0])
recursive_cast_surface_to_pickleable_surface(pool[1])

with open(cache_file_path, "wb") as handle:
pickle.dump(pool, handle)

with open(cache_file_path, "rb") as handle:
pool = pickle.load(handle)

recursive_cast_pickleable_surface_to_surface(pool[0])
recursive_cast_pickleable_surface_to_surface(pool[1])

return pool


def inner_create_troop_sprite_pool(self, who_todo, preview=False, specific_preview=None, max_preview_size=200):
weapon_list = self.troop_data.weapon_list
animation_sprite_pool = {}
status_animation_pool = {}
Expand Down
40 changes: 40 additions & 0 deletions gamescript/create_troop_sprite_pool_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pygame

class PickleableSurface:

def __init__(self, surface):
self.surface = surface

def __getstate__(self):
return ( pygame.image.tobytes(self.surface,"RGBA"), self.surface.get_size() )



def __setstate__(self, state):
self.surface = pygame.image.frombytes(state[0],state[1],"RGBA")




def recursive_cast_surface_to_pickleable_surface(_dict):

for k,v in _dict.items():
if type(v) == dict:
recursive_cast_surface_to_pickleable_surface(v)
elif type(v) == pygame.Surface:
_dict[k] = PickleableSurface(v)
elif type(v) == tuple:
_dict[k] = tuple([ c if type(c) != pygame.Surface else PickleableSurface(c) for c in _dict[k]])


def recursive_cast_pickleable_surface_to_surface(_dict):

for k,v in _dict.items():
if type(v) == dict:
recursive_cast_pickleable_surface_to_surface(v)
if type(v) == PickleableSurface:
_dict[k] = v.surface
elif type(v) == tuple:
_dict[k] = tuple([ c if type(c) != PickleableSurface else c.surface for c in _dict[k]])


5 changes: 5 additions & 0 deletions gamescript/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@ class Game:
file_name = entry.name[:-4]
elif ".py" in entry.name:
file_name = entry.name[:-3]

if file_name.startswith("."): continue

exec(f"from gamescript.common.game import " + file_name)
exec(f"" + file_name + " = " + file_name + "." + file_name)


# Will be changed in change_game_genre function depending on selected genre
troop_sprite_size = (200, 200)
start_zoom_mode = "Follow"

Expand Down
1 change: 1 addition & 0 deletions pep8.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pep8 --max-line-length=180 --first --exclude='env' $(git diff HEAD main --name-only)
60 changes: 60 additions & 0 deletions tests/test_create_troop_sprite_pool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from unittest.mock import patch

def test_pickleable_surface():
import pickle
import pygame
#from gamescript.common.game.create_troop_sprite_pool import PickleableSurface
from gamescript.create_troop_sprite_pool_methods import PickleableSurface
import io

surface = pygame.Surface((64,)*2)
pygame.draw.circle( surface, 'red', (32,32), 32 )
ps = PickleableSurface( surface )

handle = io.BytesIO()
pickle.dump(ps,handle)


handle.seek(0)

ps2 = pickle.load(handle)

s1 = surface
s2 = ps2.surface

for x in range(64):
for y in range(64):
assert s1.get_at( (x,y) ) == s2.get_at((x,y))



@patch("gamescript.create_troop_sprite_pool_methods.PickleableSurface.__eq__",
lambda self,other : self.surface == other.surface)
def test_recursive_surface_to_pickleable_surface():
import pygame
from gamescript.create_troop_sprite_pool_methods import recursive_cast_surface_to_pickleable_surface
from gamescript.create_troop_sprite_pool_methods import PickleableSurface

surface = pygame.Surface((64,)*2)
pygame.draw.circle( surface, 'red', (32,32), 32 )



_dict = {
"a":{
"b": surface
}
}


recursive_cast_surface_to_pickleable_surface(_dict)

assert _dict == {
"a": {
"b": PickleableSurface(surface)
}
}