diff --git a/WebHostLib/autolauncher.py b/WebHostLib/autolauncher.py index 9d7b7f49..6f978211 100644 --- a/WebHostLib/autolauncher.py +++ b/WebHostLib/autolauncher.py @@ -154,8 +154,10 @@ def autogen(config: dict): while 1: time.sleep(0.1) with db_session: + # for update locks the database row(s) during transaction, preventing writes from elsewhere to_start = select( - generation for generation in Generation if generation.state == STATE_QUEUED) + generation for generation in Generation + if generation.state == STATE_QUEUED).for_update() for generation in to_start: launch_generator(generator_pool, generation) except AlreadyRunningException: diff --git a/WebHostLib/generate.py b/WebHostLib/generate.py index c33d2648..15067e13 100644 --- a/WebHostLib/generate.py +++ b/WebHostLib/generate.py @@ -4,7 +4,7 @@ import random import json import zipfile from collections import Counter -from typing import Dict, Optional as TypeOptional +from typing import Dict, Optional, Any from Utils import __version__ from flask import request, flash, redirect, url_for, session, render_template @@ -15,7 +15,7 @@ from BaseClasses import seeddigits, get_seed from Generate import handle_name, PlandoSettings import pickle -from .models import * +from .models import Generation, STATE_ERROR, STATE_QUEUED, commit, db_session, Seed, UUID from WebHostLib import app from .check import get_yaml_data, roll_options from .upload import upload_zip_to_db @@ -30,16 +30,15 @@ def get_meta(options_source: dict) -> dict: } plando_options -= {""} - meta = { + server_options = { "hint_cost": int(options_source.get("hint_cost", 10)), "forfeit_mode": options_source.get("forfeit_mode", "goal"), "remaining_mode": options_source.get("remaining_mode", "disabled"), "collect_mode": options_source.get("collect_mode", "disabled"), "item_cheat": bool(int(options_source.get("item_cheat", 1))), "server_password": options_source.get("server_password", None), - "plando_options": list(plando_options) } - return meta + return {"server_options": server_options, "plando_options": list(plando_options)} @app.route('/generate', methods=['GET', 'POST']) @@ -60,13 +59,13 @@ def generate(race=False): results, gen_options = roll_options(options, meta["plando_options"]) if race: - meta["item_cheat"] = False - meta["remaining_mode"] = "disabled" + meta["server_options"]["item_cheat"] = False + meta["server_options"]["remaining_mode"] = "disabled" if any(type(result) == str for result in results.values()): return render_template("checkResult.html", results=results) elif len(gen_options) > app.config["MAX_ROLL"]: - flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players for now. " + flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. " f"If you have a larger group, please generate it yourself and upload it.") elif len(gen_options) >= app.config["JOB_THRESHOLD"]: gen = Generation( @@ -92,23 +91,22 @@ def generate(race=False): return render_template("generate.html", race=race, version=__version__) -def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=None, sid=None): +def gen_game(gen_options, meta: Optional[Dict[str, Any]] = None, owner=None, sid=None): if not meta: - meta: Dict[str, object] = {} + meta: Dict[str, Any] = {} + + meta.setdefault("server_options", {}).setdefault("hint_cost", 10) + race = meta.setdefault("race", False) - meta.setdefault("hint_cost", 10) - race = meta.get("race", False) - del (meta["race"]) - plando_options = meta.get("plando", {"bosses", "items", "connections", "texts"}) - del (meta["plando_options"]) try: target = tempfile.TemporaryDirectory() playercount = len(gen_options) seed = get_seed() - random.seed(seed) if race: - random.seed() # reset to time-based random source + random.seed() # use time-based random source + else: + random.seed(seed) seedname = "W" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)) @@ -120,7 +118,8 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No erargs.outputname = seedname erargs.outputpath = target.name erargs.teams = 1 - erargs.plando_options = PlandoSettings.from_set(plando_options) + erargs.plando_options = PlandoSettings.from_set(meta.setdefault("plando_options", + {"bosses", "items", "connections", "texts"})) name_counter = Counter() for player, (playerfile, settings) in enumerate(gen_options.items(), 1): @@ -136,7 +135,7 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No erargs.name[player] = handle_name(erargs.name[player], player, name_counter) if len(set(erargs.name.values())) != len(erargs.name): raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}") - ERmain(erargs, seed, baked_server_options=meta) + ERmain(erargs, seed, baked_server_options=meta["server_options"]) return upload_to_db(target.name, sid, owner, race) except BaseException as e: @@ -148,7 +147,6 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No meta = json.loads(gen.meta) meta["error"] = (e.__class__.__name__ + ": " + str(e)) gen.meta = json.dumps(meta) - commit() raise