-- LegacyWorlds Beta 6 -- PostgreSQL database scripts -- -- Game updates - control functions -- -- Copyright(C) 2004-2010, DeepClone Development -- -------------------------------------------------------- /* * Start a game update cycle * -------------------------- * * This function prepares the execution of a cycle of game updates. It will * try to find the identifier of the next update, and mark all game updates * as requiring an update for this identifier. * * Returns: * tick_id The identifier of the new update cycle, or NULL if an * update was already in progress. */ DROP FUNCTION IF EXISTS sys.start_tick( ) CASCADE; CREATE FUNCTION sys.start_tick( OUT tick_id BIGINT ) LANGUAGE PLPGSQL STRICT VOLATILE SECURITY DEFINER AS $start_tick$ DECLARE n_tick BIGINT; c_tick BIGINT; BEGIN -- Get next / current tick SELECT INTO n_tick , c_tick next_tick , current_tick FROM sys.status WHERE maintenance_start IS NULL FOR UPDATE; IF NOT FOUND OR c_tick IS NOT NULL THEN tick_id := NULL; RETURN; END IF; -- Prepare game updates UPDATE sys.updates SET update_last = n_tick , update_state = 'FUTURE' WHERE update_last < n_tick; -- Update system status UPDATE sys.status SET current_tick = n_tick , next_tick = n_tick + 1; tick_id := n_tick; END; $start_tick$; REVOKE EXECUTE ON FUNCTION sys.start_tick( ) FROM PUBLIC; GRANT EXECUTE ON FUNCTION sys.start_tick( ) TO :dbuser; -- -- Marks a tick as completed -- CREATE OR REPLACE FUNCTION sys.end_tick( IN tick_id BIGINT ) RETURNS VOID STRICT VOLATILE SECURITY INVOKER AS $$ BEGIN UPDATE events.events SET status = 'READY' WHERE status = 'TICK' AND tick = tick_id; UPDATE sys.status SET current_tick = NULL; PERFORM msgs.deliver_internal( ); END; $$ LANGUAGE plpgsql; /* * Check if a game update cycle got "stuck" * ----------------------------------------- * * Check sys.status for an in-progress game update identifier. If there is * one, check if there are further updates to execute in the cycle in * question. * * Returns: * tick_id The stuck tick's identifier, or NULL if there is no stuck * tick or if maintenance mode is enabled */ DROP FUNCTION IF EXISTS sys.check_stuck_tick( ) CASCADE; CREATE FUNCTION sys.check_stuck_tick( OUT tick_id BIGINT ) LANGUAGE PLPGSQL STRICT VOLATILE SECURITY DEFINER AS $check_stuck_tick$ DECLARE c_tick BIGINT; u_count INT; BEGIN -- Get next / current tick SELECT INTO c_tick current_tick FROM sys.status WHERE maintenance_start IS NULL FOR UPDATE; IF NOT FOUND OR c_tick IS NULL THEN tick_id := NULL; RETURN; END IF; -- Are there any updates left? SELECT INTO u_count count(*) FROM sys.updates WHERE update_state = 'FUTURE' AND update_last = c_tick; IF u_count = 0 THEN PERFORM sys.end_tick( c_tick ); tick_id := NULL; ELSE tick_id := c_tick; END IF; END; $check_stuck_tick$; REVOKE EXECUTE ON FUNCTION sys.check_stuck_tick( ) FROM PUBLIC; GRANT EXECUTE ON FUNCTION sys.check_stuck_tick( ) TO :dbuser; /* * Process game updates * --------------------- * * This function checks for game update elements, marking some of them for * processing depending on their type. If there are no more updates to run, * end the update cycle; otherwise, depending on the type of update, process * the items internally or return a value that indicates they are to be * processed by the external Java code. * * Parameters: * _current_update Current game update's identifier * * Returns: * _has_more TRUE if the function must be called again, FALSE * otherwise * _process_externally NULL if there is no update to process or if the * updates were processed in the database, or * the name of the update type if external * processing is needed. */ DROP FUNCTION IF EXISTS sys.process_updates( BIGINT ) CASCADE; CREATE FUNCTION sys.process_updates( IN _current_update BIGINT , OUT _has_more BOOLEAN , OUT _process_externally TEXT ) LANGUAGE PLPGSQL STRICT VOLATILE SECURITY DEFINER AS $process_updates$ DECLARE _current_type INT; _batch_size INT; _type_name TEXT; _proc_name NAME; BEGIN -- Mark all entries that were being processed as having been processed UPDATE sys.updates SET update_state = 'PROCESSED' WHERE update_state = 'PROCESSING' AND update_last = _current_update; -- Find the next type of update to process and its specific parameters SELECT INTO _current_type , _batch_size , _type_name , _proc_name updtype_id , updtype_batch_size , updtype_name , updtype_proc_name FROM sys.update_types INNER JOIN sys.updates USING ( updtype_id , updtgt_id ) WHERE update_state = 'FUTURE' AND update_last = _current_update ORDER BY updtype_ordering LIMIT 1; _has_more := FOUND; IF _has_more THEN -- Check batch size IF _batch_size IS NULL THEN _batch_size := sys.get_constant( 'game.batchSize' ); END IF; -- Mark at most _batch_size entries of the right type as being updated UPDATE sys.updates SET update_state = 'PROCESSING' WHERE update_id IN ( SELECT update_id FROM sys.updates WHERE updtype_id = _current_type AND update_state = 'FUTURE' AND update_last = _current_update LIMIT _batch_size ); IF _proc_name IS NULL THEN -- External processing is required _process_externally := _type_name; ELSE -- Process updates using a stored procedure EXECUTE 'SELECT sys."' || _proc_name || '"( $1 )' USING _current_update; END IF; ELSE -- No updates left to run PERFORM sys.end_tick( _current_update ); END IF; END; $process_updates$; REVOKE EXECUTE ON FUNCTION sys.process_updates( BIGINT ) FROM PUBLIC; GRANT EXECUTE ON FUNCTION sys.process_updates( BIGINT ) TO :dbuser;