diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
deleted file mode 100644
index 3973457..0000000
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/GameUpdateBean.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.deepclone.lw.beans.updates;
-
-
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallback;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import com.deepclone.lw.cmd.admin.logs.LogLevel;
-import com.deepclone.lw.interfaces.eventlog.Logger;
-import com.deepclone.lw.interfaces.eventlog.SystemLogger;
-import com.deepclone.lw.interfaces.game.UpdatesDAO;
-import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
-import com.deepclone.lw.interfaces.sys.SystemStatus;
-import com.deepclone.lw.interfaces.sys.TickStatusException;
-import com.deepclone.lw.interfaces.sys.Ticker;
-import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
-
-
-
-public class GameUpdateBean
- implements InitializingBean , Runnable
-{
- private Ticker ticker;
- private SystemStatus systemStatus;
- private SystemLogger logger;
-
- private TransactionTemplate tTemplate;
- private UpdatesDAO updatesDao;
-
-
- @Autowired( required = true )
- public void setTicker( Ticker ticker )
- {
- this.ticker = ticker;
- }
-
-
- @Autowired( required = true )
- public void setSystemStatus( SystemStatus systemStatus )
- {
- this.systemStatus = systemStatus;
- }
-
-
- @Autowired( required = true )
- public void setLogger( Logger logger )
- {
- this.logger = logger.getSystemLogger( "GameUpdate" );
- }
-
-
- @Autowired( required = true )
- public void setTransactionManager( PlatformTransactionManager transactionManager )
- {
- this.tTemplate = new TransactionTemplate( transactionManager );
- }
-
-
- @Autowired( required = true )
- public void setUpdatesDAO( UpdatesDAO updatesDao )
- {
- this.updatesDao = updatesDao;
- }
-
-
- @Override
- public void afterPropertiesSet( )
- {
- try {
- this.endPreviousTick( );
- } catch ( MaintenanceStatusException e ) {
- // Do nothing
- }
- this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
- }
-
-
- @Override
- public void run( )
- {
- // Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
- // being processed
- try {
- this.endPreviousTick( );
- } catch ( MaintenanceStatusException e1 ) {
- return;
- }
-
- // Initiate next tick
- long tickId;
- try {
- tickId = this.systemStatus.startTick( );
- } catch ( TickStatusException e ) {
- throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
- } catch ( MaintenanceStatusException e ) {
- return;
- }
-
- // Execute tick
- this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
- this.executeTick( tickId );
- }
-
-
- private void endPreviousTick( )
- throws MaintenanceStatusException
- {
- Long currentTick = this.systemStatus.checkStuckTick( );
- if ( currentTick == null ) {
- return;
- }
-
- this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
- this.executeTick( currentTick.longValue( ) );
- }
-
-
- private void executeTick( final long tickId )
- {
- boolean hasMore;
- do {
- hasMore = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
-
- @Override
- public Boolean doInTransaction( TransactionStatus status )
- {
- return updatesDao.processUpdates( tickId );
- }
-
- } );
- } while ( hasMore );
- this.logger.log( LogLevel.TRACE , "Tick " + tickId + " completed" ).flush( );
- }
-
-}
diff --git a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
deleted file mode 100644
index 77a1bbd..0000000
--- a/legacyworlds-server-beans-simple/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.deepclone.lw.beans.updates;
-
-
-import java.sql.Types;
-import java.util.Map;
-
-import javax.sql.DataSource;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.SqlOutParameter;
-import org.springframework.jdbc.core.SqlParameter;
-import org.springframework.jdbc.core.simple.SimpleJdbcCall;
-
-import com.deepclone.lw.interfaces.game.UpdatesDAO;
-
-
-
-public class UpdatesDAOBean
- implements UpdatesDAO
-{
-
- private SimpleJdbcCall process;
-
-
- @Autowired( required = true )
- public void setDataSource( DataSource dataSource )
- {
- this.process = new SimpleJdbcCall( dataSource );
- this.process.withCatalogName( "sys" ).withFunctionName( "process_updates" );
- this.process.withoutProcedureColumnMetaDataAccess( );
- this.process.addDeclaredParameter( new SqlParameter( "tick_id" , Types.BIGINT ) );
- this.process.addDeclaredParameter( new SqlOutParameter( "has_more" , Types.BOOLEAN ) );
- }
-
-
- @Override
- public boolean processUpdates( long tickId )
- {
- Map< String , Object > m = this.process.execute( tickId );
- return (Boolean) m.get( "has_more" );
- }
-
-}
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
index ffd1068..a6219d9 100644
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
+++ b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple-beans.xml
@@ -10,13 +10,11 @@
-
-
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml b/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
deleted file mode 100644
index 54534ff..0000000
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/game-update-bean.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
diff --git a/legacyworlds-server-beans-updates/pom.xml b/legacyworlds-server-beans-updates/pom.xml
new file mode 100644
index 0000000..d719f44
--- /dev/null
+++ b/legacyworlds-server-beans-updates/pom.xml
@@ -0,0 +1,17 @@
+
+ 4.0.0
+
+
+ legacyworlds-server-beans
+ com.deepclone.lw
+ 1.0.0
+ ../legacyworlds-server-beans/pom.xml
+
+
+ legacyworlds-server-beans-updates
+ Legacy Worlds - Server - Components - Game updates
+ ${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}
+ This module contains the components which process the game updates and allow the administration interface to display information about them.
+
+
\ No newline at end of file
diff --git a/legacyworlds-server-beans-updates/src/main/java/.empty b/legacyworlds-server-beans-updates/src/main/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java
new file mode 100644
index 0000000..73da48f
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateProcessorBean.java
@@ -0,0 +1,238 @@
+package com.deepclone.lw.beans.updates;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cmd.admin.logs.LogLevel;
+import com.deepclone.lw.interfaces.eventlog.Logger;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
+import com.deepclone.lw.interfaces.sys.SystemStatus;
+import com.deepclone.lw.interfaces.sys.TickStatusException;
+
+
+
+/**
+ * Game update processor component
+ *
+ *
+ * This component implements game updates processing. It will update the system status as necessary,
+ * checking for maintenance mode, and of course implement the necessary operations for game updates.
+ *
+ * @author E. Benoît
+ */
+class GameUpdateProcessorBean
+ implements GameUpdateProcessor
+{
+ /** System state management component */
+ private SystemStatus systemStatus;
+
+ /** Logger used by this component */
+ private SystemLogger logger;
+
+ /** Transaction template used to run update batches */
+ private TransactionTemplate tTemplate;
+
+ /** Update processing database interface */
+ private UpdatesDAO updatesDao;
+
+ /** Server-level update processors registry */
+ private ServerProcessorRegistry registry;
+
+ /** Game update processor lock */
+ private boolean executing;
+
+
+ /**
+ * Dependency injector that sets the sytstem state management component
+ *
+ * @param systemStatus
+ * the sytstem state management component
+ */
+ @Autowired( required = true )
+ public void setSystemStatus( SystemStatus systemStatus )
+ {
+ this.systemStatus = systemStatus;
+ }
+
+
+ /**
+ * Dependency injector that initialises the logger from the logging component
+ *
+ * @param logger
+ * the logging component
+ */
+ @Autowired( required = true )
+ public void setLogger( Logger logger )
+ {
+ this.logger = logger.getSystemLogger( "GameUpdate" );
+ }
+
+
+ /**
+ * Dependency injector that initialises the transaction template from the transaction manager
+ *
+ * @param transactionManager
+ * the transaction manager
+ */
+ @Autowired( required = true )
+ public void setTransactionManager( PlatformTransactionManager transactionManager )
+ {
+ this.tTemplate = new TransactionTemplate( transactionManager );
+ }
+
+
+ /**
+ * Dependency injector that sets the database access component for game updates
+ *
+ * @param updatesDao
+ * the database access component for game updates
+ */
+ @Autowired( required = true )
+ public void setUpdatesDAO( UpdatesDAO updatesDao )
+ {
+ this.updatesDao = updatesDao;
+ }
+
+
+ /**
+ * Dependency injector that sets the processor registry
+ *
+ * @param registry
+ * the processor registry
+ */
+ @Autowired( required = true )
+ public void setRegistry( ServerProcessorRegistry registry )
+ {
+ this.registry = registry;
+ }
+
+
+ /**
+ * Try and lock the processor
+ *
+ *
+ * Test the {@link #executing} field, setting it to true
and returning
+ * true
if it wasn't or returning false
if it was.
+ */
+ @Override
+ public synchronized boolean tryLock( )
+ {
+ if ( this.executing ) {
+ return false;
+ }
+ this.executing = true;
+ return true;
+ }
+
+
+ /**
+ * Unlock the processor
+ *
+ *
+ * Set {@link #executing} back to false
. If it was already set to
+ * false
, throw an {@link IllegalStateException}.
+ */
+ @Override
+ public synchronized void unlock( )
+ throws IllegalStateException
+ {
+ if ( !this.executing ) {
+ throw new IllegalStateException( "Trying to unlock game update processor, but it isn't locked" );
+ }
+ this.executing = false;
+ }
+
+
+ /**
+ * Check for an unfinished update cycle and terminate it
+ *
+ *
+ * Check for a "stuck" update cycle. If there is one, issue a warning and process it through the
+ * {@link #executeTick(long)} method.
+ */
+ @Override
+ public boolean endPreviousCycle( )
+ {
+ Long currentTick;
+ try {
+ currentTick = this.systemStatus.checkStuckTick( );
+ } catch ( MaintenanceStatusException e ) {
+ return true;
+ }
+
+ if ( currentTick == null ) {
+ return false;
+ }
+
+ this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
+ this.executeTick( currentTick );
+ return true;
+ }
+
+
+ /**
+ * Execute an update cycle
+ *
+ *
+ * Start by checking for, and executing if necessary, a "stuck" update cycle. If one is found,
+ * return. Otherwise, start a new tick through the system state manager, and call
+ * {@link #executeTick(long)} to process it.
+ *
+ *
+ * If maintenance mode is active, the new tick will not be started.
+ */
+ @Override
+ public void executeUpdateCycle( )
+ {
+ // Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
+ // being processed
+ if ( this.endPreviousCycle( ) ) {
+ return;
+ }
+
+ // Initiate next tick
+ long tickId;
+ try {
+ tickId = this.systemStatus.startTick( );
+ } catch ( TickStatusException e ) {
+ throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
+ } catch ( MaintenanceStatusException e ) {
+ return;
+ }
+
+ // Execute tick
+ this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
+ this.executeTick( tickId );
+ }
+
+
+ /**
+ * Process all remaining update batches for an update cycle
+ *
+ *
+ * This method will try and process all update batches using a {@link GameUpdateTransaction}
+ * instance. It loops until the transaction indicates that no further updates are to be
+ * processed. However, the loop will exit if maintenance mode becomes active between update
+ * batches.
+ *
+ * @param tickId
+ * the game update cycle's identifier
+ */
+ private void executeTick( final long tickId )
+ {
+ GameUpdateTransaction transaction = new GameUpdateTransaction( this.updatesDao , this.registry , tickId );
+ while ( !this.tTemplate.execute( transaction ) ) {
+ if ( this.systemStatus.checkMaintenance( ) != null ) {
+ this.logger.log( LogLevel.INFO , "Tick " + tickId + " interrupted by system maintenance" ).flush( );
+ return;
+ }
+ }
+ this.logger.log( LogLevel.TRACE , "Tick " + tickId + " completed" ).flush( );
+ }
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java
new file mode 100644
index 0000000..6878e1b
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTaskBean.java
@@ -0,0 +1,96 @@
+package com.deepclone.lw.beans.updates;
+
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
+import com.deepclone.lw.interfaces.sys.Ticker;
+import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
+
+
+
+/**
+ * Game update ticker task
+ *
+ *
+ * This component will register itself as a ticker task to be executed once per minute. When it
+ * runs, it will try and execute an update cycle.
+ *
+ * @author E. Benoît
+ */
+class GameUpdateTaskBean
+ implements InitializingBean , Runnable
+{
+ /** The ticker component */
+ private Ticker ticker;
+
+ /** The game updates processor */
+ private GameUpdateProcessor gameUpdateProcessor;
+
+
+ /**
+ * Dependency injector that sets the ticker component
+ *
+ * @param ticker
+ * the ticker component
+ */
+ @Autowired( required = true )
+ public void setTicker( Ticker ticker )
+ {
+ this.ticker = ticker;
+ }
+
+
+ /**
+ * Dependency injector that sets the game update processor
+ *
+ * @param gameUpdateProcessor
+ * the game update processor
+ */
+ @Autowired( required = true )
+ public void setGameUpdateProcessor( GameUpdateProcessor gameUpdateProcessor )
+ {
+ this.gameUpdateProcessor = gameUpdateProcessor;
+ }
+
+
+ /**
+ * Finish a previous update cycle if there is one, then register the component to the ticker
+ */
+ @Override
+ public void afterPropertiesSet( )
+ {
+ if ( this.gameUpdateProcessor.tryLock( ) ) {
+
+ try {
+ this.gameUpdateProcessor.endPreviousCycle( );
+ } finally {
+ this.gameUpdateProcessor.unlock( );
+ }
+
+ }
+ this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
+ }
+
+
+ /**
+ * Run a game update cycle
+ *
+ *
+ * Try locking the game update processor. On success, execute an update cycle, then unlock the
+ * processor.
+ */
+ @Override
+ public void run( )
+ {
+ if ( !this.gameUpdateProcessor.tryLock( ) ) {
+ return;
+ }
+ try {
+ this.gameUpdateProcessor.executeUpdateCycle( );
+ } finally {
+ this.gameUpdateProcessor.unlock( );
+ }
+ }
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java
new file mode 100644
index 0000000..b040555
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/GameUpdateTransaction.java
@@ -0,0 +1,89 @@
+package com.deepclone.lw.beans.updates;
+
+
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+
+
+
+/**
+ * Game update transaction
+ *
+ *
+ * This class is responsible for executing the necessary operations during a game update
+ * transaction. It is used by the {@link GameUpdateProcessorBean} component to implement its main
+ * processing loop.
+ *
+ * @author E. Benoît
+ */
+class GameUpdateTransaction
+ implements TransactionCallback< Boolean >
+{
+ /** The game updates database access interface */
+ private final UpdatesDAO updatesDao;
+
+ /** The registry of server-side update batch processors */
+ private final ServerProcessorRegistry registry;
+
+ /** The identifier of the current update cycle */
+ private final long tickId;
+
+
+ /**
+ * Initialise the various dependencies and set the identifier of the current cycle
+ *
+ * @param updatesDao
+ * the game updates database access interface
+ * @param registry
+ * the registry of server-side update batch processors
+ * @param tickId
+ * the identifier of the current update cycle
+ */
+ GameUpdateTransaction( UpdatesDAO updatesDao , ServerProcessorRegistry registry , long tickId )
+ {
+ this.updatesDao = updatesDao;
+ this.registry = registry;
+ this.tickId = tickId;
+ }
+
+
+ /**
+ * Update batch processing transaction
+ *
+ *
+ * Call the database's game update processing function, returning immediately if the function
+ * indicates that the cycle has been completed. Otherwise, execute a server-side processor if
+ * necessary.
+ *
+ *
+ * If the database requests server-side processing but no processor is found, an
+ * {@link UnsupportedUpdateException} will be thrown.
+ *
+ * @return true
if the current update cycle has been completed, false
+ * if more updates are needed.
+ */
+ @Override
+ public Boolean doInTransaction( TransactionStatus status )
+ {
+ GameUpdateResult result = this.updatesDao.processUpdates( this.tickId );
+ String local = result.getLocalExecution( );
+ if ( result.isFinished( ) ) {
+ return true;
+ } else if ( local == null ) {
+ return false;
+ }
+
+ UpdateBatchProcessor processor = this.registry.getProcessorFor( local );
+ if ( processor == null ) {
+ throw new UnsupportedUpdateException( local );
+ }
+
+ processor.processBatch( this.tickId );
+ return false;
+ }
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java
new file mode 100644
index 0000000..423c7cc
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistry.java
@@ -0,0 +1,33 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * Server-side update processor registry
+ *
+ *
+ * This interface allows server-side game update batch processors to be looked up depending on the
+ * type of the updates to process.
+ *
+ * @author E. Benoît
+ *
+ */
+interface ServerProcessorRegistry
+{
+
+ /**
+ * Get a batch processor for some type of update
+ *
+ * @param type
+ * the type of updates to process, as stored in the sys.update_types
+ * database table
+ *
+ * @return null
if no processor for the specified type has been registered, or the
+ * processor to use.
+ */
+ public UpdateBatchProcessor getProcessorFor( String type );
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java
new file mode 100644
index 0000000..9055ae9
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/ServerProcessorRegistryBean.java
@@ -0,0 +1,68 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * Server-side update processor registry component
+ *
+ *
+ * This component will look through registered Spring components for update processors, and use the
+ * results as the registry's contents.
+ *
+ * @author E. Benoît
+ */
+class ServerProcessorRegistryBean
+ implements ServerProcessorRegistry , BeanPostProcessor
+{
+
+ /** Known server-side update batch processors */
+ private final Map< String , UpdateBatchProcessor > processors = new HashMap< String , UpdateBatchProcessor >( );
+
+
+ /** Do nothing - we're not interested in uninitialised components */
+ @Override
+ public Object postProcessBeforeInitialization( Object bean , String beanName )
+ {
+ return bean;
+ }
+
+
+ /**
+ * If an initialised component implements the {@link UpdateBatchProcessor} interface, add it to
+ * the {@link #processors} map. A {@link BeanInitializationException} will be thrown if two
+ * components seem to process the same update type.
+ */
+ @Override
+ public Object postProcessAfterInitialization( Object bean , String beanName )
+ throws BeansException
+ {
+ if ( bean instanceof UpdateBatchProcessor ) {
+ UpdateBatchProcessor processor = (UpdateBatchProcessor) bean;
+ String updType = processor.getUpdateType( );
+ if ( this.processors.containsKey( updType ) ) {
+ throw new BeanInitializationException( "Duplicate update processor for update type " + updType );
+ }
+ this.processors.put( updType , processor );
+ }
+ return bean;
+ }
+
+
+ /** Obtain the processor from the {@link #processors} map */
+ @Override
+ public UpdateBatchProcessor getProcessorFor( String type )
+ {
+ return this.processors.get( type );
+ }
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java
new file mode 100644
index 0000000..c167bf1
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UnsupportedUpdateException.java
@@ -0,0 +1,29 @@
+package com.deepclone.lw.beans.updates;
+
+
+/**
+ * Unsupported update exception
+ *
+ *
+ * This exception is thrown by the {@link GameUpdateTransaction} when there is no server-side
+ * processor for some update type.
+ *
+ * @author E. Benoît
+ */
+@SuppressWarnings( "serial" )
+public class UnsupportedUpdateException
+ extends RuntimeException
+{
+
+ /**
+ * Initialise the exception
+ *
+ * @param name
+ * the name of the update type for which no processor is registered.
+ */
+ UnsupportedUpdateException( String name )
+ {
+ super( "No processor found for update type " + name );
+ }
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
new file mode 100644
index 0000000..8de9681
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/java/com/deepclone/lw/beans/updates/UpdatesDAOBean.java
@@ -0,0 +1,69 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.sql.Types;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+/**
+ * Updates data access component
+ *
+ *
+ * This component accesses the database procedures and views which constitute the game update
+ * system.
+ *
+ * @author E. Benoît
+ */
+class UpdatesDAOBean
+ implements UpdatesDAO
+{
+
+ /** Game update processor stored procedure */
+ private StoredProc process;
+
+
+ /**
+ * Dependency injector for the data source
+ *
+ *
+ * When Spring injects the data source component, initialise the stored procedures used by the
+ * component.
+ *
+ * @param dataSource
+ * the data source component
+ */
+ @Autowired( required = true )
+ public void setDataSource( DataSource dataSource )
+ {
+ this.process = new StoredProc( dataSource , "sys" , "process_updates" ).addParameter( "tick_id" , Types.BIGINT )
+ .addOutput( "_has_more" , Types.BOOLEAN ).addOutput( "_process_externally" , Types.VARCHAR );
+ }
+
+
+ /**
+ * Execute the processor's stored procedure
+ *
+ *
+ * This implementation simply runs the database's sys.process_updates()
stored
+ * procedure, and converts the two values it returns into a {@link GameUpdateResult} instance.
+ */
+ @Override
+ public GameUpdateResult processUpdates( long tickId )
+ {
+ Map< String , Object > m = this.process.execute( tickId );
+ if ( !(Boolean) m.get( "_has_more" ) ) {
+ return new GameUpdateResult( );
+ }
+ return new GameUpdateResult( (String) m.get( "_process_externally" ) );
+ }
+
+}
diff --git a/legacyworlds-server-beans-updates/src/main/resources/.empty b/legacyworlds-server-beans-updates/src/main/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml b/legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
similarity index 61%
rename from legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
rename to legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
index eb24044..687e57a 100644
--- a/legacyworlds-server-beans-simple/src/main/resources/configuration/simple/updates-dao-bean.xml
+++ b/legacyworlds-server-beans-updates/src/main/resources/configuration/game.xml
@@ -2,8 +2,8 @@
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
-
+
-
+
\ No newline at end of file
diff --git a/legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml b/legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml
new file mode 100644
index 0000000..b957642
--- /dev/null
+++ b/legacyworlds-server-beans-updates/src/main/resources/configuration/game/updates.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/legacyworlds-server-beans-updates/src/test/java/.empty b/legacyworlds-server-beans-updates/src/test/java/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans-updates/src/test/resources/.empty b/legacyworlds-server-beans-updates/src/test/resources/.empty
new file mode 100644
index 0000000..e69de29
diff --git a/legacyworlds-server-beans/pom.xml b/legacyworlds-server-beans/pom.xml
index 6e138de..a03d945 100644
--- a/legacyworlds-server-beans/pom.xml
+++ b/legacyworlds-server-beans/pom.xml
@@ -21,15 +21,16 @@
- ../legacyworlds-server-beans-i18n
- ../legacyworlds-server-beans-eventlog
../legacyworlds-server-beans-accounts
+ ../legacyworlds-server-beans-bt
+ ../legacyworlds-server-beans-eventlog
+ ../legacyworlds-server-beans-i18n
../legacyworlds-server-beans-mailer
- ../legacyworlds-server-beans-system
../legacyworlds-server-beans-naming
../legacyworlds-server-beans-resources
- ../legacyworlds-server-beans-bt
- ../legacyworlds-server-beans-user
../legacyworlds-server-beans-simple
+ ../legacyworlds-server-beans-system
+ ../legacyworlds-server-beans-updates
+ ../legacyworlds-server-beans-user
diff --git a/legacyworlds-server-data/db-structure/database.sql b/legacyworlds-server-data/db-structure/database.sql
index 50b7913..9552e38 100644
--- a/legacyworlds-server-data/db-structure/database.sql
+++ b/legacyworlds-server-data/db-structure/database.sql
@@ -48,4 +48,4 @@ BEGIN;
COMMIT;
/* Delete loader script */
-\! rm loader.tmp
\ No newline at end of file
+\! rm loader.tmp
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql b/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
index a3c76bb..ef0b8d4 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/140-status.sql
@@ -1,7 +1,7 @@
-- LegacyWorlds Beta 6
-- PostgreSQL database scripts
--
--- System & game updates status
+-- System & ticker status
--
-- Copyright(C) 2004-2010, DeepClone Development
-- --------------------------------------------------------
@@ -53,55 +53,3 @@ INSERT INTO sys.ticker( task_name , status )
VALUES ( 'Game update' , 'STOPPED' );
GRANT SELECT ON sys.ticker TO :dbuser;
-
-
-
---
--- Updates
---
-CREATE TABLE sys.updates(
- id BIGSERIAL NOT NULL PRIMARY KEY ,
- gu_type update_type NOT NULL ,
- status processing_status NOT NULL DEFAULT 'FUTURE' ,
- last_tick BIGINT NOT NULL DEFAULT -1
-);
-
-CREATE INDEX idx_updates_finder
- ON sys.updates (gu_type, status, last_tick);
-
-
---
--- Planet updates
---
-CREATE TABLE verse.updates(
- update_id BIGINT NOT NULL PRIMARY KEY ,
- planet_id INT NOT NULL
-);
-
-CREATE INDEX idx_planetupdates_planet
- ON verse.updates (planet_id);
-
-ALTER TABLE verse.updates
- ADD CONSTRAINT fk_planetupdates_update
- FOREIGN KEY ( update_id ) REFERENCES sys.updates ,
- ADD CONSTRAINT fk_planetupdates_planet
- FOREIGN KEY ( planet_id ) REFERENCES verse.planets;
-
-
---
--- Empire updates
---
-CREATE TABLE emp.updates(
- update_id BIGINT NOT NULL PRIMARY KEY ,
- empire_id INT NOT NULL
-);
-
-CREATE INDEX idx_empireupdates_empire
- ON emp.updates( empire_id );
-
-ALTER TABLE emp.updates
- ADD CONSTRAINT fk_empireupdates_update
- FOREIGN KEY ( update_id ) REFERENCES sys.updates ,
- ADD CONSTRAINT fk_empireupdates_empire
- FOREIGN KEY ( empire_id ) REFERENCES emp.empires
- ON DELETE CASCADE;
diff --git a/legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql b/legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql
new file mode 100644
index 0000000..8ce13f5
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/030-data/145-updates.sql
@@ -0,0 +1,172 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates control tables
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Update targets
+ * ---------------
+ *
+ * An update target refers to a table whose rows are the units to be updated
+ * by some step of the game update.
+ *
+ * Inserting rows into this control table will generate a new, specific table
+ * to store references, and a set of triggers.
+ */
+DROP TABLE IF EXISTS sys.update_targets CASCADE;
+CREATE TABLE sys.update_targets(
+ /* Automatically generated identifier of the target type */
+ updtgt_id SERIAL NOT NULL PRIMARY KEY ,
+
+ /* Name of the target, displayed in the administration interface */
+ updtgt_name VARCHAR( 32 ) NOT NULL ,
+
+ /* Schema in which the target table resides */
+ updtgt_schema NAME NOT NULL ,
+
+ /* Name of the target table */
+ updtgt_table NAME NOT NULL
+);
+
+/* Update target names are unique */
+CREATE UNIQUE INDEX idx_updtgt_name
+ ON sys.update_targets ( LOWER( updtgt_name ) );
+/* Schema / table combinations are unique */
+CREATE UNIQUE INDEX idx_updtgt_target
+ ON sys.update_targets ( updtgt_schema , updtgt_table );
+
+
+
+/*
+ * Update type definitions
+ * ------------------------
+ *
+ * An update type corresponds to a procedure which will be applied at each
+ * game update cycle (once per minute, unless defaults have been modified),
+ * and which performs a computation or set thereof on the game's data.
+ */
+DROP TABLE IF EXISTS sys.update_types CASCADE;
+CREATE TABLE sys.update_types(
+ /* Automatically generated identifier of the update type */
+ updtype_id SERIAL NOT NULL ,
+
+ /* The type of target this update refers to */
+ updtgt_id INT NOT NULL ,
+
+ /* Name of the update type, used in the administration interface and when
+ * updates need to be executed externally.
+ */
+ updtype_name VARCHAR( 64 ) NOT NULL ,
+
+ /* Ordering index of the update type. This field is always re-generated
+ * when update types are added or removed.
+ */
+ updtype_ordering INT NOT NULL ,
+
+ /* Description of the update type to be included in the administration
+ * interface.
+ */
+ updtype_description TEXT NOT NULL ,
+
+ /* Name of the stored procedure which handles the update. When this is
+ * NULL, the update type is assumed to be supported externally (i.e. by
+ * the game server) rather than internally. Otherwise, a stored procedure
+ * bearing that name, accepting a BIGINT as its parameter and returning
+ * VOID, must exist in the sys schema.
+ */
+ updtype_proc_name NAME ,
+
+ /* Size of the update batch. If this is NULL, the global value from the
+ * game.batchSize constant will be used.
+ */
+ updtype_batch_size INT ,
+
+ /* The primary key includes both the update type identifier and the target
+ * identifier for coherence reasons.
+ */
+ PRIMARY KEY( updtype_id , updtgt_id ) ,
+
+ /* Batch sizes are either NULL or strictly positive */
+ CHECK( updtype_batch_size IS NULL
+ OR updtype_batch_size > 0 )
+);
+
+/* Update names must be unique, independently of the case */
+CREATE UNIQUE INDEX idx_updtype_name
+ ON sys.update_types ( LOWER( updtype_name ) );
+/* Update ordering index must be unique */
+CREATE UNIQUE INDEX idx_updtype_ordering
+ ON sys.update_types ( updtype_ordering );
+/* Procedure names must be unique */
+CREATE UNIQUE INDEX idx_updtype_procname
+ ON sys.update_types ( updtype_proc_name );
+
+
+
+ALTER TABLE sys.update_types
+ ADD CONSTRAINT fk_updtype_target
+ FOREIGN KEY ( updtgt_id ) REFERENCES sys.update_targets( updtgt_id );
+
+
+
+/*
+ * Update state type
+ * ------------------
+ *
+ * This type represents the possible states of a game update
+ */
+DROP TYPE IF EXISTS sys.update_state_type CASCADE;
+CREATE TYPE sys.update_state_type
+ AS ENUM (
+ /* The row will be included in the current game update */
+ 'FUTURE' ,
+ /* The row is being processed */
+ 'PROCESSING' ,
+ /* The row has been processed by the current or previous game update */
+ 'PROCESSED'
+ );
+
+
+/*
+ * Main updates table
+ * -------------------
+ *
+ * This table lists all update rows, including their type and target, as well
+ * as their state.
+ */
+DROP TABLE IF EXISTS sys.updates CASCADE;
+CREATE TABLE sys.updates(
+ /* The update row's automatically generated identifier */
+ update_id BIGSERIAL NOT NULL ,
+
+ /* The type of update this row is about */
+ updtype_id INT NOT NULL ,
+
+ /* The type of target for the update */
+ updtgt_id INT NOT NULL ,
+
+ /* The update row's current state */
+ update_state sys.update_state_type
+ NOT NULL
+ DEFAULT 'FUTURE' ,
+
+ /* The tick identifier corresponding to the last game update in which
+ * this row was processed.
+ */
+ update_last BIGINT NOT NULL
+ DEFAULT -1 ,
+
+ /* The primary key includes the automatically generated identifier but
+ * also the type and target type.
+ */
+ PRIMARY KEY( update_id , updtype_id , updtgt_id )
+);
+
+ALTER TABLE sys.updates
+ ADD CONSTRAINT fk_update_type
+ FOREIGN KEY ( updtype_id , updtgt_id ) REFERENCES sys.update_types
+ ON DELETE CASCADE;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql b/legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql
new file mode 100644
index 0000000..0a2e5f3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/003-updates.sql
@@ -0,0 +1,571 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - support functions for update definitions
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+
+/*
+ * Type that represents a field from a primary key
+ * ------------------------------------------------
+ *
+ * This type represents both the field itself (as a name) and its type.
+ */
+DROP TYPE IF EXISTS sys.pk_field_type CASCADE;
+CREATE TYPE sys.pk_field_type AS (
+ f_name NAME ,
+ f_type NAME
+);
+
+
+/*
+ * Get a table's primary key fields and field types
+ * -------------------------------------------------
+ *
+ * This function will list all fields from a table's primary key along with
+ * their types.
+ *
+ * Parameters:
+ * _schema_name The name of the schema the table resides in
+ * _table_name The name of the table
+ *
+ * Returns:
+ * ? A set of sys.pk_field_type records
+ */
+DROP FUNCTION IF EXISTS sys.get_table_pkey( NAME , NAME ) CASCADE;
+CREATE FUNCTION sys.get_table_pkey( _schema_name NAME , _table_name NAME )
+ RETURNS SETOF sys.pk_field_type
+ LANGUAGE SQL
+ STRICT IMMUTABLE
+ SECURITY INVOKER
+AS $get_table_pkey$
+
+ SELECT _attr.attname AS f_name , _type.typname AS f_type
+
+ FROM pg_namespace _schema
+ INNER JOIN pg_class _table
+ ON _table.relnamespace = _schema.oid
+ INNER JOIN (
+ SELECT indrelid , unnest( indkey ) AS indattnum
+ FROM pg_index WHERE indisprimary
+ ) _index ON _index.indrelid = _table.oid
+ INNER JOIN pg_attribute _attr
+ ON _attr.attrelid = _table.oid
+ AND _attr.attnum = _index.indattnum
+ INNER JOIN pg_type _type
+ ON _type.oid = _attr.atttypid
+
+ WHERE _schema.nspname = $1
+ AND _table.relname = $2
+
+ ORDER BY _index.indattnum;
+
+$get_table_pkey$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.get_table_pkey( NAME , NAME )
+ FROM PUBLIC;
+
+
+
+/*
+ * Automatic update row insertion
+ * -------------------------------
+ *
+ * This trigger function is added to all type-specific update tables. It is
+ * called before insertions, and will make sure a generic update row exists
+ * for the new row.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_speupd_before_insert( ) CASCADE;
+CREATE FUNCTION sys.tgf_speupd_before_insert( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_speupd_before_insert$
+
+DECLARE
+ _update_id BIGINT;
+
+BEGIN
+
+ INSERT INTO sys.updates ( updtype_id , updtgt_id )
+ VALUES ( NEW.updtype_id , NEW.updtgt_id )
+ RETURNING update_id INTO _update_id;
+
+ NEW.update_id := _update_id;
+ RETURN NEW;
+END;
+$tgf_speupd_before_insert$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_speupd_before_insert( )
+ FROM PUBLIC;
+
+
+
+/*
+ * Automatic update row removal
+ * -----------------------------
+ *
+ * This trigger function is added to all type-specific update tables. It is
+ * called once a row has been deleted, and will remove the corresponding
+ * generic update row.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_speupd_after_delete( ) CASCADE;
+CREATE FUNCTION sys.tgf_speupd_after_delete( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_speupd_after_delete$
+BEGIN
+
+ DELETE FROM sys.updates
+ WHERE update_id = OLD.update_id
+ AND updtype_id = OLD.updtype_id
+ AND updtgt_id = OLD.updtgt_id;
+
+ RETURN OLD;
+END;
+$tgf_speupd_after_delete$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_speupd_after_delete( )
+ FROM PUBLIC;
+
+
+
+/*
+ * Add missing update rows
+ * ------------------------
+ *
+ * This function adds rows missing from the update and specific update tables
+ * for a given target type.
+ *
+ * Parameters:
+ * _target_type The identifier of the target type for which missing
+ * rows are to be inserted.
+ */
+DROP FUNCTION IF EXISTS sys.insert_missing_updates( INT ) CASCADE;
+CREATE FUNCTION sys.insert_missing_updates( _target_type INT )
+ RETURNS VOID
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $insert_missing_updates$
+
+DECLARE
+ _t_schema TEXT;
+ _t_table TEXT;
+ _field_list TEXT;
+ _query TEXT;
+
+BEGIN
+ SELECT INTO _t_schema , _t_table updtgt_schema , updtgt_table
+ FROM sys.update_targets
+ WHERE updtgt_id = _target_type;
+ SELECT INTO _field_list array_to_string( array_agg( f_name ) , ' , ' )
+ FROM sys.get_table_pkey( _t_schema , _t_table );
+
+ _query := 'INSERT INTO "' || _t_schema || '"."' || _t_table || '_updates" '
+ || '( updtgt_id , updtype_id , ' || _field_list || ') '
+ || 'SELECT updtgt_id , updtype_id , ' || _field_list
+ || ' FROM sys.update_types CROSS JOIN "'
+ || _t_schema || '"."' || _t_table || '" LEFT OUTER JOIN "'
+ || _t_schema || '"."' || _t_table
+ || '_updates" USING ( updtgt_id , updtype_id , ' || _field_list
+ || ') WHERE update_id IS NULL AND updtgt_id = ' || _target_type;
+ EXECUTE _query;
+
+END;
+$insert_missing_updates$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.insert_missing_updates( INT )
+ FROM PUBLIC;
+
+
+
+/*
+ * Trigger function that adds missing update rows
+ * ----------------------------------------------
+ *
+ * This function calls sys.insert_missing_updates. The main difference between
+ * the original function and this one is that the former cannot be used in
+ * triggers, while the latter is meant to be used as a trigger on the target
+ * table.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_insert_missing_updates( ) CASCADE;
+CREATE FUNCTION sys.tgf_insert_missing_updates( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_insert_missing_updates$
+BEGIN
+ IF TG_NARGS <> 1 THEN
+ RAISE EXCEPTION 'This trigger function requires one argument';
+ END IF;
+ PERFORM sys.insert_missing_updates( TG_ARGV[ 0 ]::INT );
+ RETURN NULL;
+END;
+$tgf_insert_missing_updates$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_insert_missing_updates( )
+ FROM PUBLIC;
+
+
+/*
+ * Handle new update types
+ * ------------------------
+ *
+ * This function is triggered when a new update type is created; it will insert
+ * the update rows for the new type.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtype_after_insert_row( ) CASCADE;
+CREATE FUNCTION sys.tgf_updtype_after_insert_row( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_updtype_after_insert$
+BEGIN
+ PERFORM sys.insert_missing_updates( NEW.updtgt_id );
+ RETURN NEW;
+END;
+$tgf_updtype_after_insert$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_updtype_after_insert_row( )
+ FROM PUBLIC;
+
+CREATE TRIGGER tg_updtype_after_insert_row
+ AFTER INSERT ON sys.update_types
+ FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtype_after_insert_row( );
+
+
+/*
+ * Update type definition check
+ * -----------------------------
+ *
+ * This trigger function is called when a new update type is added or when
+ * an update type's stored procedure is updated. It makes sure that the
+ * corresponding stored procedure actually exists.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_check_update_type_proc( ) CASCADE;
+CREATE FUNCTION sys.tgf_check_update_type_proc( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_check_update_type_proc$
+BEGIN
+
+ IF NEW.updtype_proc_name IS NULL THEN
+ RETURN NEW;
+ END IF;
+
+ PERFORM 1
+ FROM pg_namespace _schema
+ INNER JOIN pg_proc _proc
+ ON _proc.pronamespace = _schema.oid
+ WHERE _schema.nspname = 'sys'
+ AND _proc.proname = NEW.updtype_proc_name;
+ IF NOT FOUND THEN
+ RAISE EXCEPTION 'Update type % has invalid update function sys.%' ,
+ NEW.updtype_name , NEW.updtype_proc_name;
+ END IF;
+
+ RETURN NEW;
+END;
+$tgf_check_update_type_proc$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_check_update_type_proc( )
+ FROM PUBLIC;
+
+CREATE TRIGGER tg_check_update_type_proc_insert
+ BEFORE INSERT ON sys.update_types
+ FOR EACH ROW EXECUTE PROCEDURE sys.tgf_check_update_type_proc( );
+CREATE TRIGGER tg_check_update_type_proc_update
+ BEFORE UPDATE OF updtype_proc_name ON sys.update_types
+ FOR EACH ROW EXECUTE PROCEDURE sys.tgf_check_update_type_proc( );
+
+
+/*
+ * Trigger that reorders the update types
+ * ---------------------------------------
+ *
+ * This function will update the ordering field of update types whenever rows
+ * are inserted or deleted.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_reorder_update_types( ) CASCADE;
+CREATE FUNCTION sys.tgf_reorder_update_types( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_reorder_update_types$
+
+DECLARE
+ _max_ordering INT;
+
+BEGIN
+
+ SELECT INTO _max_ordering MAX( updtype_ordering ) FROM sys.update_types;
+ IF _max_ordering IS NOT NULL THEN
+ UPDATE sys.update_types
+ SET updtype_ordering = updtype_ordering + _max_ordering * 2;
+ END IF;
+
+ UPDATE sys.update_types
+ SET updtype_ordering = updtype_rownumber * 2
+ FROM (
+ SELECT updtype_id AS id,
+ row_number( ) OVER (
+ ORDER BY updtype_ordering
+ ) AS updtype_rownumber
+ FROM sys.update_types
+ ) _row
+ WHERE updtype_id = _row.id;
+
+ RETURN NULL;
+END;
+$tgf_reorder_update_types$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_reorder_update_types( )
+ FROM PUBLIC;
+
+CREATE TRIGGER tg_updtype_after_insert_stmt
+ AFTER INSERT ON sys.update_types
+ FOR EACH STATEMENT EXECUTE PROCEDURE sys.tgf_reorder_update_types( );
+
+CREATE TRIGGER tg_updtype_after_delete_stmt
+ AFTER DELETE ON sys.update_types
+ FOR EACH STATEMENT EXECUTE PROCEDURE sys.tgf_reorder_update_types( );
+
+
+
+/*
+ * Check target tables
+ * --------------------
+ *
+ * Before a new row is inserted into sys.update_targets, make sure
+ * the target table it contains actually exists.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtgt_before_insert( ) CASCADE;
+CREATE FUNCTION sys.tgf_updtgt_before_insert( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_updtgt_before_insert$
+BEGIN
+ PERFORM 1
+ FROM pg_class pc
+ INNER JOIN pg_namespace pn
+ ON pn.oid = pc.relnamespace
+ INNER JOIN pg_roles pr
+ ON pr.oid = pc.relowner
+ WHERE pn.nspname = NEW.updtgt_schema
+ AND pc.relname = NEW.updtgt_table
+ AND pc.reltype <> 0 AND pc.relkind = 'r'
+ AND pr.rolname <> 'postgres';
+ IF NOT FOUND THEN
+ RAISE EXCEPTION 'Update target table %.% not found' ,
+ NEW.updtgt_schema , NEW.updtgt_table
+ USING ERRCODE = 'foreign_key_violation';
+ END IF;
+
+ RETURN NEW;
+END;
+$tgf_updtgt_before_insert$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_updtgt_before_insert( )
+ FROM PUBLIC;
+
+CREATE TRIGGER tg_updtgt_before_insert
+ BEFORE INSERT ON sys.update_targets
+ FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtgt_before_insert( );
+
+
+
+/*
+ * Create update list and triggers for target table
+ * -------------------------------------------------
+ *
+ * After a new update type has been registered, a table listing updates
+ * as well as a set of triggers on the target table need to be created.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtgt_after_insert( ) CASCADE;
+CREATE FUNCTION sys.tgf_updtgt_after_insert( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ AS $tgf_updtgt_after_insert$
+
+DECLARE
+ _table_name TEXT;
+ _query TEXT;
+ _field_name NAME;
+ _field_type NAME;
+ _field_list TEXT;
+
+BEGIN
+ _table_name := '"' || NEW.updtgt_schema || '"."' || NEW.updtgt_table || '_updates"';
+ _query := 'CREATE TABLE ' || _table_name || $_table_base_fields$(
+ update_id BIGINT NOT NULL ,
+ updtype_id INT NOT NULL ,
+ updtgt_id INT NOT NULL
+ $_table_base_fields$;
+
+ -- List target table's primary key fields
+ _field_list := '';
+ FOR _field_name , _field_type IN
+ SELECT * FROM sys.get_table_pkey( NEW.updtgt_schema , NEW.updtgt_table )
+ LOOP
+ _query := _query || ', "' || _field_name || '" "' || _field_type
+ || '" NOT NULL';
+ IF _field_list <> '' THEN
+ _field_list := _field_list || ' , ';
+ END IF;
+ _field_list := _field_list || '"' || _field_name || '"';
+ END LOOP;
+
+ _query := _query ||
+ ' , PRIMARY KEY ( updtgt_id , updtype_id , update_id , '
+ || _field_list || ' ) , CHECK ( updtgt_id = ' || NEW.updtgt_id || ' ) )';
+ EXECUTE _query;
+
+ -- Add foreign keys
+ _query := 'ALTER TABLE ' || _table_name
+ || ' ADD CONSTRAINT fk_upd_' || NEW.updtgt_table || '_update '
+ || 'FOREIGN KEY ( update_id , updtype_id , updtgt_id ) REFERENCES sys.updates '
+ || 'ON DELETE CASCADE , '
+ || ' ADD CONSTRAINT fk_upd_' || NEW.updtgt_table || '_target '
+ || 'FOREIGN KEY ( ' || _field_list
+ || ' ) REFERENCES "' || NEW.updtgt_schema || '"."' || NEW.updtgt_table
+ || '" ON DELETE CASCADE';
+ EXECUTE _query;
+
+ -- Create automatic update creation/deletion triggers
+ _query := 'CREATE TRIGGER tg_speupd_before_insert BEFORE INSERT ON ' || _table_name
+ || ' FOR EACH ROW EXECUTE PROCEDURE sys.tgf_speupd_before_insert( )';
+ EXECUTE _query;
+ _query := 'CREATE TRIGGER tg_speupd_after_delete AFTER DELETE ON ' || _table_name
+ || ' FOR EACH ROW EXECUTE PROCEDURE sys.tgf_speupd_after_delete( )';
+ EXECUTE _query;
+
+ -- Create triggers that will insert/delete update rows when new items are added/removed
+ _query := 'CREATE TRIGGER tg_speupd_after_insert AFTER INSERT ON "'
+ || NEW.updtgt_schema || '"."' || NEW.updtgt_table
+ || '" FOR EACH STATEMENT EXECUTE PROCEDURE sys.tgf_insert_missing_updates( '
+ || NEW.updtgt_id || ' )';
+ EXECUTE _query;
+
+ RETURN NEW;
+END;
+$tgf_updtgt_after_insert$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_updtgt_after_insert( )
+ FROM PUBLIC;
+
+CREATE TRIGGER tg_updtgt_after_insert
+ AFTER INSERT ON sys.update_targets
+ FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtgt_after_insert( );
+
+
+/*
+ * Trigger function that deletes specific update tables
+ * -----------------------------------------------------
+ *
+ * This trigger function is called after a row has been deleted from the
+ * update target definitions table. It will destroy the corresponding table.
+ */
+DROP FUNCTION IF EXISTS sys.tgf_updtgt_after_delete( );
+CREATE FUNCTION sys.tgf_updtgt_after_delete( )
+ RETURNS TRIGGER
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $tgf_updtgt_after_delete$
+DECLARE
+ _query TEXT;
+BEGIN
+ _query := 'DROP TABLE "' || OLD.updtgt_schema || '"."' || OLD.updtgt_table || '_updates"';
+ EXECUTE _query;
+ RETURN NULL;
+END;
+$tgf_updtgt_after_delete$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.tgf_updtgt_after_delete( )
+ FROM PUBLIC;
+
+CREATE TRIGGER tg_updtgt_after_delete
+ AFTER DELETE ON sys.update_targets
+ FOR EACH ROW EXECUTE PROCEDURE sys.tgf_updtgt_after_delete( );
+
+
+
+/*
+ * Stored update type registration
+ * --------------------------------
+ *
+ * This function can be called to register an update type that uses a stored
+ * procedure. The new update type is added at the end of the list of updates.
+ *
+ * Since this function is meant to be used from the SQL definition code only,
+ * it does not handle errors and will raise exceptions when something goes
+ * wrong.
+ *
+ * Parameters:
+ * _target The name of the target for this update type
+ * _name The name of the update type
+ * _description The update type's description
+ * _proc The name of the stored procedure
+ */
+DROP FUNCTION IF EXISTS sys.register_update_type( TEXT , TEXT , TEXT , NAME );
+CREATE FUNCTION sys.register_update_type( _target TEXT , _name TEXT , _description TEXT , _proc NAME )
+ RETURNS VOID
+ LANGUAGE PLPGSQL
+ STRICT VOLATILE
+ SECURITY INVOKER
+ AS $register_update_type$
+
+DECLARE
+ _target_id INT;
+ _max_ordering INT;
+
+BEGIN
+ SELECT INTO _target_id updtgt_id
+ FROM sys.update_targets
+ WHERE updtgt_name = _target;
+
+ SELECT INTO _max_ordering MAX( updtype_ordering )
+ FROM sys.update_types;
+ IF _max_ordering IS NULL THEN
+ _max_ordering := 1;
+ END IF;
+
+ INSERT INTO sys.update_types(
+ updtgt_id , updtype_name , updtype_ordering ,
+ updtype_description , updtype_proc_name
+ ) VALUES (
+ _target_id , _name , _max_ordering + 1 ,
+ _description , _proc
+ );
+END;
+$register_update_type$;
+
+
+REVOKE EXECUTE
+ ON FUNCTION sys.register_update_type( TEXT , TEXT , TEXT , NAME )
+ FROM PUBLIC;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
index 7963876..63ac90e 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/040-empire.sql
@@ -45,18 +45,6 @@ BEGIN
-- Add empire resources
INSERT INTO emp.resources ( empire_id , resource_name_id )
SELECT _name_id , resource_name_id FROM defs.resources;
-
- -- Add empire update records
- FOR _update_type IN SELECT _type
- FROM unnest( enum_range( NULL::update_type ) ) AS _type
- WHERE _type::text LIKE 'EMPIRE_%'
- LOOP
- INSERT INTO sys.updates( gu_type )
- VALUES ( _update_type )
- RETURNING id INTO _update;
- INSERT INTO emp.updates ( update_id , empire_id )
- VALUES ( _update , _name_id );
- END LOOP;
END;
$$ LANGUAGE plpgsql;
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
index 79a7f62..ca6718e 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
@@ -196,17 +196,6 @@ BEGIN
-- FIXME: for now, just stick data about resources in the appropriate table
INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
SELECT pnid , resource_name_id FROM defs.resources;
-
- -- Add planet update records
- FOR utp IN SELECT x FROM unnest( enum_range( NULL::update_type ) ) AS x
- WHERE x::text LIKE 'PLANET_%'
- LOOP
- INSERT INTO sys.updates( gu_type )
- VALUES ( utp )
- RETURNING id INTO uid;
- INSERT INTO verse.updates ( update_id , planet_id )
- VALUES ( uid , pnid );
- END LOOP;
END;
$$ LANGUAGE plpgsql;
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql b/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
index 9133bf8..7ab8252 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/000-updates-ctrl.sql
@@ -8,17 +8,29 @@
---
--- Start a tick
---
-
-CREATE OR REPLACE FUNCTION sys.start_tick( OUT tick_id BIGINT )
+/*
+ * 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 $$
+ 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
@@ -31,17 +43,27 @@ BEGIN
END IF;
-- Prepare game updates
- UPDATE sys.updates SET last_tick = n_tick , status = 'FUTURE'
- WHERE last_tick < n_tick;
+ 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;
+ UPDATE sys.status
+ SET current_tick = n_tick ,
+ next_tick = n_tick + 1;
tick_id := n_tick;
-END;
-$$ LANGUAGE plpgsql;
-GRANT EXECUTE ON FUNCTION sys.start_tick( ) TO :dbuser;
+END;
+$start_tick$;
+
+REVOKE EXECUTE
+ ON FUNCTION sys.start_tick( )
+ FROM PUBLIC;
+GRANT EXECUTE
+ ON FUNCTION sys.start_tick( )
+ TO :dbuser;
@@ -64,17 +86,29 @@ $$ LANGUAGE plpgsql;
---
--- Check if a tick got "stuck"
---
-
-CREATE OR REPLACE FUNCTION sys.check_stuck_tick( OUT tick_id BIGINT )
+/*
+ * 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 $$
+ AS $check_stuck_tick$
+
DECLARE
c_tick BIGINT;
u_count INT;
+
BEGIN
-- Get next / current tick
SELECT INTO c_tick current_tick
@@ -87,71 +121,123 @@ BEGIN
END IF;
-- Are there any updates left?
- SELECT INTO u_count count(*) FROM sys.updates
- WHERE status = 'FUTURE' AND last_tick = c_tick;
+ 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;
-$$ LANGUAGE plpgsql;
+$check_stuck_tick$;
-GRANT EXECUTE ON FUNCTION sys.check_stuck_tick( ) TO :dbuser;
+REVOKE EXECUTE
+ ON FUNCTION sys.check_stuck_tick( )
+ FROM PUBLIC;
+GRANT EXECUTE
+ ON FUNCTION sys.check_stuck_tick( )
+ TO :dbuser;
---
--- Process game updates
---
--- Parameters:
--- c_tick Current tick
---
--- Returns:
--- TRUE if the function must be called again, FALSE otherwise
---
-
-CREATE OR REPLACE FUNCTION sys.process_updates( IN c_tick BIGINT , OUT has_more BOOLEAN )
+/*
+ * 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 $$
+ AS $process_updates$
+
DECLARE
- b_size INT;
- p_utype update_type;
- utype update_type;
- uid BIGINT;
+ _current_type INT;
+ _batch_size INT;
+ _type_name TEXT;
+ _proc_name NAME;
+
BEGIN
- b_size := sys.get_constant( 'game.batchSize' );
- p_utype := NULL;
+ -- 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;
- -- Mark at most b_size entries as being updated
- FOR uid , utype IN SELECT id , gu_type FROM sys.updates
- WHERE last_tick = c_tick AND status = 'FUTURE'
- ORDER BY gu_type LIMIT b_size
- LOOP
- IF p_utype IS NULL THEN
- p_utype := utype;
+ -- 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;
- EXIT WHEN utype <> p_utype;
- UPDATE sys.updates SET status = 'PROCESSING' WHERE id = uid;
- END LOOP;
+
+ -- 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
+ );
- has_more := p_utype IS NOT NULL;
- IF has_more THEN
- -- Execute actual updates
- EXECUTE 'SELECT sys.process_' || lower( p_utype::TEXT ) || '_updates( $1 )'
- USING c_tick;
- UPDATE sys.updates SET status = 'PROCESSED'
- WHERE status = 'PROCESSING' AND last_tick = c_tick;
+ 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
- -- If nothing was found, we're done
- PERFORM sys.end_tick( c_tick );
+ -- No updates left to run
+ PERFORM sys.end_tick( _current_update );
END IF;
END;
-$$ LANGUAGE plpgsql;
+$process_updates$;
-GRANT EXECUTE ON FUNCTION sys.process_updates( BIGINT ) TO :dbuser;
+REVOKE EXECUTE
+ ON FUNCTION sys.process_updates( BIGINT )
+ FROM PUBLIC;
+GRANT EXECUTE
+ ON FUNCTION sys.process_updates( BIGINT )
+ TO :dbuser;
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql b/legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql
new file mode 100644
index 0000000..8fa557e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/005-core-update-targets.sql
@@ -0,0 +1,16 @@
+-- LegacyWorlds Beta 6
+-- PostgreSQL database scripts
+--
+-- Game updates - core update targets
+--
+-- Copyright(C) 2004-2012, DeepClone Development
+-- --------------------------------------------------------
+
+INSERT INTO sys.update_targets (
+ updtgt_name , updtgt_schema , updtgt_table
+ ) VALUES (
+ 'Planets' , 'verse' , 'planets'
+ ) , (
+ 'Empires' , 'emp' , 'empires'
+ );
+
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql b/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
index a0bb8b1..26e3088 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/010-empire-money.sql
@@ -18,13 +18,14 @@ DECLARE
c_debt REAL;
BEGIN
-- Lock empires for update
- PERFORM e.name_id FROM sys.updates su
- INNER JOIN emp.updates eu
- ON eu.update_id = su.id
+ PERFORM e.name_id
+ FROM sys.updates su
+ INNER JOIN emp.empires_updates eu
+ USING ( updtgt_id , updtype_id , update_id )
INNER JOIN emp.empires e
- ON eu.empire_id = e.name_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'EMPIRE_MONEY'
+ USING ( name_id )
+ WHERE su.update_last = c_tick
+ AND su.update_state = 'PROCESSING'
FOR UPDATE OF e;
-- Select all money-related data from empires being updated
@@ -32,14 +33,16 @@ BEGIN
( pov.planet_income - pov.planet_upkeep ) AS p_money ,
fov.fleet_upkeep AS f_money , ( v.status = 'PROCESSED' ) AS on_vacation
FROM sys.updates su
- INNER JOIN emp.updates eu ON eu.update_id = su.id
- INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+ INNER JOIN emp.empires_updates eu
+ USING ( updtgt_id , updtype_id , update_id )
+ INNER JOIN emp.empires e
+ USING ( name_id )
INNER JOIN emp.fleets_overview fov ON fov.empire = e.name_id
INNER JOIN emp.planets_overview pov ON pov.empire = e.name_id
INNER JOIN naming.empire_names en ON en.id = e.name_id
LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'EMPIRE_MONEY'
+ WHERE su.update_last = c_tick
+ AND su.update_state = 'PROCESSING'
LOOP
-- Compute new cash reserve
c_cash := 0;
@@ -78,4 +81,12 @@ BEGIN
WHERE name_id = rec.id;
END LOOP;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireMoney' ,
+ 'Empires'' money is being updated using the previous update''s results. '
+ || 'This update type should disappear, as everything it does will '
+ || 'be replaced by the resources update.' ,
+ 'process_empire_money_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
index 89c40e2..6004945 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/015-empire-resources.sql
@@ -42,17 +42,16 @@ AS $process_empire_resources_updates$
END
FROM sys.updates _upd_sys
- INNER JOIN emp.updates _upd_emp
- ON _upd_emp.update_id = _upd_sys.id
+ INNER JOIN emp.empires_updates _upd_emp
+ USING ( updtgt_id , updtype_id , update_id )
INNER JOIN emp.planets _emp_planets
- USING ( empire_id )
+ ON empire_id = name_id
INNER JOIN verse.planet_resources _pl_resources
USING ( planet_id )
- WHERE _upd_sys.last_tick = $1
- AND _upd_sys.status = 'PROCESSING'
- AND _upd_sys.gu_type = 'EMPIRE_RESOURCES'
- AND _emp_resources.empire_id = _upd_emp.empire_id
+ WHERE _upd_sys.update_last = $1
+ AND _upd_sys.update_state = 'PROCESSING'
+ AND _emp_resources.empire_id = _upd_emp.name_id
AND _emp_resources.resource_name_id = _pl_resources.resource_name_id;
$process_empire_resources_updates$ LANGUAGE SQL;
@@ -61,3 +60,10 @@ $process_empire_resources_updates$ LANGUAGE SQL;
REVOKE EXECUTE
ON FUNCTION sys.process_empire_resources_updates( BIGINT )
FROM PUBLIC;
+
+
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireResources' ,
+ 'Empires'' resources are being updated using the previous update''s results. ' ,
+ 'process_empire_resources_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
index d9e3cfe..152f83f 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/020-empire-research.sql
@@ -18,28 +18,31 @@ DECLARE
tu_rec RECORD;
BEGIN
-- Lock empires for update and planets for share
- PERFORM e.name_id FROM sys.updates su
- INNER JOIN emp.updates eu ON eu.update_id = su.id
- INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+ PERFORM e.name_id
+ FROM sys.updates _upd_sys
+ INNER JOIN emp.empires_updates eu
+ USING ( updtgt_id , updtype_id , update_id )
+ INNER JOIN emp.empires e USING ( name_id )
INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
INNER JOIN verse.planets p ON p.name_id = ep.planet_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'EMPIRE_RESEARCH'
+ WHERE _upd_sys.update_last = c_tick
+ AND _upd_sys.update_state = 'PROCESSING'
FOR UPDATE OF e
FOR SHARE OF ep , p;
-- Process empires
FOR rec IN SELECT e.name_id AS id , ( v.status = 'PROCESSED' ) AS on_vacation ,
sum( p.population ) AS population
- FROM sys.updates su
- INNER JOIN emp.updates eu ON eu.update_id = su.id
- INNER JOIN emp.empires e ON eu.empire_id = e.name_id
+ FROM sys.updates _upd_sys
+ INNER JOIN emp.empires_updates eu
+ USING ( updtgt_id , updtype_id , update_id )
+ INNER JOIN emp.empires e USING ( name_id )
INNER JOIN emp.planets ep ON ep.empire_id = e.name_id
INNER JOIN verse.planets p ON p.name_id = ep.planet_id
INNER JOIN naming.empire_names en ON en.id = e.name_id
LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'EMPIRE_RESEARCH'
+ WHERE _upd_sys.update_last = c_tick
+ AND _upd_sys.update_state = 'PROCESSING'
GROUP BY e.name_id , v.status
LOOP
-- Insert any missing tech line
@@ -83,4 +86,9 @@ BEGIN
END LOOP;
END LOOP;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireResearch' ,
+ 'Empire research points are being attributed to technologies.' ,
+ 'process_empire_research_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql b/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
index 7bcaa9f..aa1e849 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/025-empire-debt.sql
@@ -25,11 +25,13 @@ BEGIN
bld_dr := sys.get_constant( 'game.debt.buildings');
FOR empire, debt IN SELECT e.name_id AS id , e.debt
- FROM sys.updates su
- INNER JOIN emp.updates eu ON eu.update_id = su.id
- INNER JOIN emp.empires e ON eu.empire_id = e.name_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'EMPIRE_DEBT' AND e.debt > 0
+ FROM sys.updates _upd_sys
+ INNER JOIN emp.empires_updates eu
+ USING ( updtgt_id , updtype_id , update_id )
+ INNER JOIN emp.empires e USING ( name_id )
+ WHERE _upd_sys.update_last = c_tick
+ AND _upd_sys.update_state = 'PROCESSING'
+ AND e.debt > 0
FOR UPDATE
LOOP
PERFORM sys.write_log( 'EmpireDebt' , 'DEBUG'::log_level , 'Handling debt for empire #'
@@ -59,4 +61,9 @@ BEGIN
PERFORM verse.handle_debt( empire , upkeep , debt , bld_dr );
END LOOP;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Empires' , 'EmpireDebt' ,
+ 'The effects of empires'' debts are being computed.' ,
+ 'process_empire_debt_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql b/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
index c9dd59e..b5a939b 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/030-fleet-arrivals.sql
@@ -7,7 +7,7 @@
-- --------------------------------------------------------
-CREATE OR REPLACE FUNCTION sys.process_planet_fleet_arrivals_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_fleet_arrivals_updates( c_tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -18,14 +18,16 @@ DECLARE
f_ids BIGINT[];
BEGIN
-- Lock all records
- PERFORM f.id FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ PERFORM 1
+ FROM sys.updates su
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
INNER JOIN emp.empires e ON e.name_id = f.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND f.status = 'AVAILABLE' AND fm.time_left = 1
FOR UPDATE;
@@ -34,8 +36,10 @@ BEGIN
f.owner_id AS fleet_owner , ( v.status = 'PROCESSED' AND b.id IS NULL ) AS on_vacation ,
bool_or( f.attacking ) AS attacking
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
LEFT OUTER JOIN emp.planets ep ON ep.planet_id = p.name_id
@@ -43,9 +47,8 @@ BEGIN
LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
LEFT OUTER JOIN battles.battles b
ON b.location_id = p.name_id AND b.last_tick IS NULL
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
- AND f.status = 'AVAILABLE' AND fm.time_left = 1
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+ AND f.status = 'AVAILABLE' AND fm.time_left = 1
GROUP BY p.name_id , ep.empire_id , f.owner_id , v.status , b.id
LOOP
-- Fleets owned by the planet's owner are never attacking, same for fleets arriving on
@@ -103,16 +106,17 @@ BEGIN
SELECT f.location_id , ln.name , f.owner_id , fon.name ,
f.name , fs.power , f.attacking , fm.source_id , sn.name
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
INNER JOIN fleets.stats_view fs ON fs.id = f.id
INNER JOIN naming.empire_names fon ON fon.id = f.owner_id
INNER JOIN naming.map_names ln ON ln.id = f.location_id
INNER JOIN naming.map_names sn ON sn.id = fm.source_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND f.status = 'AVAILABLE' AND fm.time_left = 1;
-- Delete movement records, set redeployment penalties, update battles
@@ -120,15 +124,16 @@ BEGIN
f.attacking AS attacking , b.id AS battle ,
f.location_id AS location
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
INNER JOIN fleets.stats_view fs ON fs.id = f.id
LEFT OUTER JOIN battles.battles b
ON b.location_id = p.name_id AND b.last_tick IS NULL
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_ARRIVALS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND f.status = 'AVAILABLE' AND fm.time_left = 1
LOOP
DELETE FROM fleets.movements
@@ -145,4 +150,10 @@ BEGIN
-- Send fleet arrival events
PERFORM events.commit_fleet_arrivals( c_tick );
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+
+SELECT sys.register_update_type( 'Planets' , 'FleetArrivals' ,
+ 'Fleets which were one update away are arriving at their destinations.' ,
+ 'process_fleet_arrivals_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql b/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
index 184aa0a..2151325 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/040-fleet-movements.sql
@@ -7,7 +7,7 @@
-- --------------------------------------------------------
-CREATE OR REPLACE FUNCTION sys.process_planet_fleet_movements_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_fleet_movements_updates( c_tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -18,13 +18,14 @@ DECLARE
BEGIN
-- Lock all records
PERFORM f.id FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
INNER JOIN emp.empires e ON e.name_id = f.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_MOVEMENTS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND f.status = 'AVAILABLE'
FOR UPDATE;
@@ -36,8 +37,10 @@ BEGIN
rp.system_id AS is_ref_point_system , rp.orbit AS is_ref_point_orbit ,
rps.x AS is_ref_point_x , rps.y AS is_ref_point_y
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN verse.systems s ON s.id = p.system_id
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.ships fs ON fs.fleet_id = f.id
@@ -46,8 +49,7 @@ BEGIN
LEFT OUTER JOIN fleets.ms_system isms ON isms.movement_id = f.id
LEFT OUTER JOIN verse.planets rp ON isms.ref_point_id = rp.name_id
LEFT OUTER JOIN verse.systems rps ON rps.id = rp.system_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_MOVEMENTS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND f.status = 'AVAILABLE' AND m.state_time_left = 1
GROUP BY f.id , s.x , s.y , s.id , isms.ref_point_id , isms.outwards ,
isms.past_ref_point , rp.system_id , rp.orbit , rps.x , rps.y
@@ -98,13 +100,19 @@ BEGIN
time_left = time_left - 1
WHERE fleet_id IN (
SELECT f.id FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN fleets.movements fm ON fm.fleet_id = f.id
INNER JOIN emp.empires e ON e.name_id = f.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_MOVEMENTS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND f.status = 'AVAILABLE' );
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'FleetMovements' ,
+ 'Fleets are moving.' ,
+ 'process_fleet_movements_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql b/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
index 7249954..c7b1fca 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/050-fleet-status.sql
@@ -7,7 +7,7 @@
-- --------------------------------------------------------
-CREATE OR REPLACE FUNCTION sys.process_planet_fleet_status_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_fleet_status_updates( c_tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -18,26 +18,27 @@ DECLARE
BEGIN
-- Lock all records
PERFORM f.id FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN fleets.fleets f ON f.location_id = p.name_id
INNER JOIN emp.empires e ON e.name_id = f.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_STATUS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
FOR UPDATE;
-- Fleet deployments
FOR rec IN SELECT f.id AS fleet , f.owner_id AS owner , f.location_id AS location ,
b.id AS battle
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN fleets.fleets f ON f.location_id = vu.planet_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN fleets.fleets f ON f.location_id = vu.name_id
AND f.status = 'DEPLOYING' AND f.penalty = 1
INNER JOIN emp.empires e ON e.name_id = f.owner_id
LEFT OUTER JOIN battles.battles b
ON b.location_id = f.location_id AND b.last_tick IS NULL
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_STATUS'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
LOOP
-- Add fleet to battle (will be ignored if battle is NULL)
PERFORM battles.add_fleet( rec.battle , rec.fleet , TRUE , c_tick );
@@ -62,16 +63,21 @@ BEGIN
-- Fleets that must become available
UPDATE fleets.fleets f SET status = 'AVAILABLE' , penalty = 0
- FROM sys.updates su , verse.updates vu
- WHERE vu.update_id = su.id AND f.location_id = vu.planet_id
- AND f.penalty = 1 AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_STATUS' AND su.last_tick = c_tick;
+ FROM sys.updates su , verse.planets_updates vu
+ WHERE vu.update_id = su.update_id AND f.location_id = vu.name_id
+ AND f.penalty = 1
+ AND su.update_state = 'PROCESSING' AND su.update_last = c_tick;
-- Fleets that still have a penalty
UPDATE fleets.fleets f SET penalty = penalty - 1
- FROM sys.updates su , verse.updates vu
- WHERE vu.update_id = su.id AND f.location_id = vu.planet_id
- AND f.penalty > 1 AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_FLEET_STATUS' AND su.last_tick = c_tick;
+ FROM sys.updates su , verse.planets_updates vu
+ WHERE vu.update_id = su.update_id AND f.location_id = vu.name_id
+ AND f.penalty > 1
+ AND su.update_state = 'PROCESSING' AND su.update_last = c_tick;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'FleetStatus' ,
+ 'Fleet states (e.g. "deploying", "unavailable", etc...) are being updated.' ,
+ 'process_fleet_status_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql b/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
index d944f65..2cfb2e9 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/060-planet-battle.sql
@@ -7,7 +7,7 @@
-- --------------------------------------------------------
-CREATE OR REPLACE FUNCTION sys.process_planet_battle_start_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_battle_start_updates( c_tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -17,12 +17,14 @@ DECLARE
BEGIN
FOR p_id IN SELECT p.name_id
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
LEFT OUTER JOIN battles.battles b
ON b.location_id = p.name_id AND b.last_tick IS NULL
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_BATTLE_START' AND b.location_id IS NULL
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+ AND b.location_id IS NULL
FOR UPDATE OF p
LOOP
IF battles.check_start( p_id ) THEN
@@ -32,10 +34,13 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
+SELECT sys.register_update_type( 'Planets' , 'BattleStart' ,
+ 'Battles are being started.' ,
+ 'process_battle_start_updates'
+ );
-
-CREATE OR REPLACE FUNCTION sys.process_planet_battle_main_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_battle_main_updates( c_tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -63,13 +68,14 @@ BEGIN
FOR rec IN SELECT b.id AS battle , b.first_tick AS first_tick ,
b.location_id AS location , ( ph.current / p.population )::REAL AS happiness
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
INNER JOIN battles.battles b
ON b.location_id = p.name_id AND b.last_tick IS NULL
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_BATTLE_MAIN'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
FOR UPDATE OF p , b
LOOP
PERFORM sys.write_log( 'BattleUpdate' , 'DEBUG'::log_level , 'Handling battle #' || rec.battle
@@ -115,10 +121,14 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
+SELECT sys.register_update_type( 'Planets' , 'BattleCompute' ,
+ 'In-progress battles are being updated.' ,
+ 'process_battle_main_updates'
+ );
-CREATE OR REPLACE FUNCTION sys.process_planet_battle_end_updates( c_tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_battle_end_updates( c_tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -129,12 +139,13 @@ DECLARE
BEGIN
FOR rec IN SELECT b.id AS battle , b.location_id AS location
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN battles.battles b
ON b.location_id = p.name_id AND b.last_tick IS NULL
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_BATTLE_END'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
FOR UPDATE OF p , b
LOOP
IF battles.get_fleets_power( rec.battle , c_tick , TRUE ) = 0 THEN
@@ -179,3 +190,7 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
+SELECT sys.register_update_type( 'Planets' , 'BattleEnd' ,
+ 'Finished battles are being ended.' ,
+ 'process_battle_end_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql b/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
index 85128b7..1cdb636 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/070-planet-abandon.sql
@@ -16,24 +16,31 @@ DECLARE
p_id INT;
BEGIN
-- Lock all records
- PERFORM p.name_id FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
- INNER JOIN emp.planets ep ON p.name_id = vu.planet_id
- INNER JOIN emp.empires e ON e.name_id = ep.empire_id
- INNER JOIN emp.abandon a ON a.planet_id = p.name_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_ABANDON'
+ PERFORM p.name_id
+ FROM sys.updates su
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
+ INNER JOIN emp.planets ep
+ ON ep.planet_id = p.name_id
+ INNER JOIN emp.empires e
+ ON e.name_id = ep.empire_id
+ INNER JOIN emp.abandon a
+ ON a.planet_id = p.name_id
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
FOR UPDATE;
-- Handle planets where time has run out
FOR p_id IN SELECT p.name_id
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON p.name_id = vu.planet_id
- INNER JOIN emp.abandon a ON a.planet_id = p.name_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_ABANDON' AND a.time_left = 1
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
+ INNER JOIN emp.abandon a ON a.planet_id = p.name_id
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+ AND a.time_left = 1
LOOP
PERFORM emp.leave_planet( p_id );
END LOOP;
@@ -41,8 +48,14 @@ BEGIN
-- Update all abandon records
UPDATE emp.abandon a SET time_left = a.time_left - 1
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_ABANDON' AND a.planet_id = vu.planet_id;
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
+ AND a.planet_id = vu.name_id;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetAbandon' ,
+ 'Planets are being abandoned.' ,
+ 'process_planet_abandon_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql b/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
index 37470af..e9281bb 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/080-planet-construction.sql
@@ -46,8 +46,10 @@ BEGIN
qi.destroy AS destroy , qi.building_id AS building ,
b.work AS req_work , b.cost AS req_cost
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN emp.planets ep ON ep.planet_id = p.name_id
INNER JOIN emp.empires e ON e.name_id = ep.empire_id
INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -56,8 +58,7 @@ BEGIN
INNER JOIN tech.buildables b ON b.name_id = qi.building_id
INNER JOIN naming.empire_names en ON en.id = e.name_id
LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_CONSTRUCTION'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND ( v.account_id IS NULL OR v.status <> 'PROCESSED' )
ORDER BY e.name_id , p.name_id , qi.queue_order
FOR UPDATE OF p , e , q , qi
@@ -215,3 +216,8 @@ BEGIN
END IF;
END;
$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'CivilianConstruction' ,
+ 'Civilian build queues are being processed.' ,
+ 'process_planet_construction_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql b/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
index f3c250a..1645fac 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/090-planet-military.sql
@@ -44,8 +44,10 @@ BEGIN
qi.queue_order AS qorder , qi.amount AS amount ,
qi.ship_id AS ship , s.work AS req_work , s.cost AS req_cost
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN emp.planets ep ON ep.planet_id = p.name_id
INNER JOIN emp.empires e ON e.name_id = ep.empire_id
INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
@@ -54,8 +56,7 @@ BEGIN
INNER JOIN tech.buildables s ON s.name_id = qi.ship_id
INNER JOIN naming.empire_names en ON en.id = e.name_id
LEFT OUTER JOIN users.vacations v ON v.account_id = en.owner_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_MILITARY'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
AND ( v.account_id IS NULL OR v.status <> 'PROCESSED' )
ORDER BY e.name_id , p.name_id , qi.queue_order
FOR UPDATE OF p , e , q , qi
@@ -225,3 +226,8 @@ BEGIN
DROP TABLE blt_ships;
END;
$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'MilitaryConstruction' ,
+ 'Military build queues are being processed.' ,
+ 'process_planet_military_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql b/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
index 2a805f8..16a3d77 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/100-planet-population.sql
@@ -37,11 +37,12 @@ BEGIN
ph.target AS target , ph.current AS happy_pop ,
( ph.current / p.population )::REAL AS current
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_POPULATION'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
FOR UPDATE OF p, ph
LOOP
IF round( rec.target / rel_ch ) = round( rec.current / rel_ch ) THEN
@@ -101,4 +102,9 @@ BEGIN
END IF;
END LOOP;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetPopulation' ,
+ 'Planet populations are being updated.' ,
+ 'process_planet_population_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql b/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
index 9e59a4b..a0a6db3 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/105-planet-resource-regeneration.sql
@@ -16,7 +16,7 @@
* Parameters:
* _tick The identifier of the game update
*/
-CREATE OR REPLACE FUNCTION sys.process_planet_res_regen_updates( _tick BIGINT )
+CREATE OR REPLACE FUNCTION sys.process_res_regen_updates( _tick BIGINT )
RETURNS VOID
STRICT VOLATILE
SECURITY INVOKER
@@ -31,17 +31,21 @@ AS $process_planet_res_regen_updates$
)
FROM sys.updates _upd_sys
- INNER JOIN verse.updates _upd_verse
- ON _upd_verse.update_id = _upd_sys.id
+ INNER JOIN verse.planets_updates _upd_verse
+ USING ( update_id , updtype_id , updtgt_id )
- WHERE _upd_sys.last_tick = $1
- AND _upd_sys.status = 'PROCESSING'
- AND _upd_sys.gu_type = 'PLANET_RES_REGEN'
- AND _provider.planet_id = _upd_verse.planet_id;
+ WHERE _upd_sys.update_last = $1
+ AND _upd_sys.update_state = 'PROCESSING'
+ AND _provider.planet_id = _upd_verse.name_id;
$process_planet_res_regen_updates$ LANGUAGE SQL;
REVOKE EXECUTE
- ON FUNCTION sys.process_planet_res_regen_updates( BIGINT )
+ ON FUNCTION sys.process_res_regen_updates( BIGINT )
FROM PUBLIC;
+
+SELECT sys.register_update_type( 'Planets' , 'ResourceRegeneration' ,
+ 'Resource providers are being regenerated.' ,
+ 'process_res_regen_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql b/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
index cbcc432..1822d7f 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/110-planet-money.sql
@@ -20,13 +20,14 @@ BEGIN
( ph.current / p.population )::REAL AS happiness ,
( ea.planet_id IS NULL ) AS produces_income
FROM sys.updates su
- INNER JOIN verse.updates vu ON vu.update_id = su.id
- INNER JOIN verse.planets p ON vu.planet_id = p.name_id
+ INNER JOIN verse.planets_updates vu
+ USING ( update_id , updtype_id , updtgt_id )
+ INNER JOIN verse.planets p
+ USING ( name_id )
INNER JOIN verse.planet_happiness ph ON ph.planet_id = p.name_id
INNER JOIN verse.planet_money pm ON pm.planet_id = p.name_id
LEFT OUTER JOIN emp.abandon ea ON ea.planet_id = p.name_id
- WHERE su.last_tick = c_tick AND su.status = 'PROCESSING'
- AND su.gu_type = 'PLANET_MONEY'
+ WHERE su.update_last = c_tick AND su.update_state = 'PROCESSING'
FOR UPDATE OF p, pm
LOOP
IF rec.produces_income THEN
@@ -42,4 +43,9 @@ BEGIN
WHERE planet_id = rec.id;
END LOOP;
END;
-$$ LANGUAGE plpgsql;
\ No newline at end of file
+$$ LANGUAGE plpgsql;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetMoney' ,
+ 'Planets'' income and upkeep are being updated.' ,
+ 'process_planet_money_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
index b641762..6f9c53f 100644
--- a/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
+++ b/legacyworlds-server-data/db-structure/parts/050-updates/120-planet-mining.sql
@@ -127,12 +127,12 @@ AS $gu_pmc_get_data$
pmc_total AS total_weight
FROM sys.updates _upd_sys
- INNER JOIN verse.updates _upd_verse
- ON _upd_sys.id = _upd_verse.update_id
+ INNER JOIN verse.planets_updates _upd_verse
+ USING ( update_id , updtype_id , updtgt_id )
INNER JOIN verse.planets _planet
- ON _planet.name_id = _upd_verse.planet_id
+ USING ( name_id )
INNER JOIN verse.resource_providers _resprov
- USING ( planet_id )
+ ON planet_id = name_id
INNER JOIN verse.planet_resources _pres
USING ( planet_id , resource_name_id )
LEFT OUTER JOIN (
@@ -141,10 +141,10 @@ AS $gu_pmc_get_data$
pmc_total , _happ.current AS happiness
FROM sys.updates _upd_sys
- INNER JOIN verse.updates _upd_verse
- ON _upd_sys.id = _upd_verse.update_id
+ INNER JOIN verse.planets_updates _upd_verse
+ USING ( update_id , updtype_id , updtgt_id )
INNER JOIN emp.planets _emp_planet
- USING ( planet_id )
+ ON _emp_planet.planet_id = _upd_verse.name_id
INNER JOIN emp.empires _emp
ON _emp_planet.empire_id = _emp.name_id
INNER JOIN emp.mining_settings _emset
@@ -161,17 +161,15 @@ AS $gu_pmc_get_data$
) AS _pmset
USING ( empire_id , planet_id , resource_name_id )
- WHERE _upd_sys.last_tick = $1
- AND _upd_sys.status = 'PROCESSING'
- AND _upd_sys.gu_type = 'PLANET_MINING'
+ WHERE _upd_sys.update_last = $1
+ AND _upd_sys.update_state = 'PROCESSING'
FOR SHARE OF _emp_planet , _emp , _emset , _happ
) AS _owner
USING ( planet_id , resource_name_id )
- WHERE _upd_sys.last_tick = $1
- AND _upd_sys.status = 'PROCESSING'
- AND _upd_sys.gu_type = 'PLANET_MINING'
+ WHERE _upd_sys.update_last = $1
+ AND _upd_sys.update_state = 'PROCESSING'
FOR UPDATE OF _resprov , _pres
FOR SHARE OF _planet ;
@@ -286,3 +284,8 @@ $process_planet_mining_updates$ LANGUAGE PLPGSQL;
REVOKE EXECUTE
ON FUNCTION sys.process_planet_mining_updates( BIGINT )
FROM PUBLIC;
+
+SELECT sys.register_update_type( 'Planets' , 'PlanetMining' ,
+ 'Resources are being extracted from mines.' ,
+ 'process_planet_mining_updates'
+ );
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql
new file mode 100644
index 0000000..5438a50
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/010-target-definition.sql
@@ -0,0 +1,70 @@
+/*
+ * Tests of the update target definitions
+ */
+BEGIN;
+ -- Make sure we get rid of all existing definitions first
+ DELETE FROM sys.update_types;
+ DELETE FROM sys.update_targets;
+
+ -- Create a test schema & table which will be used as the target
+ CREATE SCHEMA test
+ CREATE TABLE test(
+ test_id SERIAL NOT NULL PRIMARY KEY
+ )
+ CREATE TABLE test2(
+ test_id SERIAL NOT NULL PRIMARY KEY
+ );
+
+ -- ***** TESTS BEGIN HERE *****
+ SELECT plan( 8 );
+
+ SELECT diag_test_name( 'Update target definitions - Failure if schema/table do not exist' );
+ SELECT throws_ok(
+ $$ INSERT INTO sys.update_targets(
+ updtgt_name , updtgt_schema , updtgt_table
+ ) VALUES (
+ 'Test' , 'test' , 'does_not_exist'
+ ); $$ ,
+ 23503
+ );
+
+ SELECT diag_test_name( 'Update target definitions - Successful insertion' );
+ SELECT lives_ok( $$
+ INSERT INTO sys.update_targets(
+ updtgt_name , updtgt_schema , updtgt_table
+ ) VALUES (
+ 'Test' , 'test' , 'test'
+ );
+ $$ );
+ SELECT diag_test_name( 'Update target definitions - Specific update table - Exists after insertion' );
+ SELECT has_table( 'test' , 'test_updates' , 'No specific update table' );
+ SELECT diag_test_name( 'Update target definitions - Specific update table - Primary key' );
+ SELECT index_is_primary( 'test' , 'test_updates' , 'test_updates_pkey' );
+ SELECT diag_test_name( 'Update target definitions - Specific update table - Primary key definition' );
+ SELECT has_index( 'test' , 'test_updates' , 'test_updates_pkey' ,
+ ARRAY[ 'updtgt_id' , 'updtype_id' , 'update_id' , 'test_id' ] );
+
+ SELECT diag_test_name( 'Update target definitions - Duplicate name' );
+ SELECT throws_ok( $$
+ INSERT INTO sys.update_targets(
+ updtgt_name , updtgt_schema , updtgt_table
+ ) VALUES (
+ 'Test' , 'test' , 'test2'
+ );
+ $$ , 23505 );
+
+ SELECT diag_test_name( 'Update target definitions - Duplicate table reference' );
+ SELECT throws_ok( $$
+ INSERT INTO sys.update_targets(
+ updtgt_name , updtgt_schema , updtgt_table
+ ) VALUES (
+ 'Test 2' , 'test' , 'test'
+ );
+ $$ , 23505 );
+
+ SELECT diag_test_name( 'Update target definitions - Deletion drops the specific update table' );
+ DELETE FROM sys.update_targets;
+ SELECT hasnt_table( 'test' , 'test_updates' , 'Specific update table still exists' );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql
new file mode 100644
index 0000000..895aa72
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/003-updates/020-type-definitions.sql
@@ -0,0 +1,150 @@
+/*
+ * Tests of the update type definitions
+ */
+BEGIN;
+ -- Make sure we get rid of all existing definitions first
+ DELETE FROM sys.update_types;
+ DELETE FROM sys.update_targets;
+
+ -- Create a test schema & tables which will be used as targets
+ CREATE SCHEMA test
+ CREATE TABLE test(
+ test_id SERIAL NOT NULL PRIMARY KEY
+ );
+
+ -- Create target definitions
+ INSERT INTO sys.update_targets(
+ updtgt_id , updtgt_name , updtgt_schema , updtgt_table
+ ) VALUES (
+ 1 , 'Test 1' , 'test' , 'test'
+ );
+
+ -- Create a dummy stored procedure in the sys schema
+ CREATE FUNCTION sys.dummy_update_func( c_tick BIGINT )
+ RETURNS INT LANGUAGE SQL AS 'SELECT 1;';
+
+ -- ***** TESTS BEGIN HERE *****
+ SELECT plan( 14 );
+
+ SELECT diag_test_name( 'Update types - Definition with invalid target' );
+ SELECT throws_ok( $$
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+ ) VALUES (
+ 1 , 5 , 'Test' , '' , 1
+ );
+ $$ , 23503 );
+
+ SELECT diag_test_name( 'Update types - Definition with invalid stored procedure name' );
+ SELECT throws_ok( $$
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering , updtype_proc_name
+ ) VALUES (
+ 1 , 5 , 'Test' , '' , 1 , 'does_not_exist'
+ );
+ $$ , 'P0001' );
+
+ SELECT diag_test_name( 'Update types - Successful insertion with no procedure name' );
+ SELECT lives_ok( $$
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+ ) VALUES (
+ 1 , 1 , 'Test' , '' , 1
+ );
+ $$ );
+ DELETE FROM sys.update_types;
+
+ SELECT diag_test_name( 'Update types - Successful insertion with a procedure name' );
+ SELECT lives_ok( $$
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering , updtype_proc_name
+ ) VALUES (
+ 1 , 1 , 'Test' , '' , 1 , 'dummy_update_func'
+ );
+ $$ );
+
+ SELECT diag_test_name( 'Update types - Setting an existing row''s stored procedure to something invalid fails' );
+ SELECT throws_ok( $$
+ UPDATE sys.update_types
+ SET updtype_proc_name = 'does_not_exist'
+ WHERE updtype_id = 1
+ $$ , 'P0001' );
+
+ SELECT diag_test_name( 'Update types - Failure on duplicate name' );
+ SELECT throws_ok( $$
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+ ) VALUES (
+ 2 , 1 , 'Test' , '' , 1
+ );
+ $$ , 23505 );
+
+ SELECT diag_test_name( 'Update types - Failure on duplicate stored procedure' );
+ SELECT throws_ok( $$
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering , updtype_proc_name
+ ) VALUES (
+ 3 , 1 , 'Test 2' , '' , 1 , 'dummy_update_func'
+ );
+ $$ , 23505 );
+
+ SELECT diag_test_name( 'Update types - Target deletion fails if type definitions exist' );
+ SELECT throws_ok( $$ DELETE FROM sys.update_targets WHERE updtgt_id = 1 $$ , 23503 );
+
+ DELETE FROM sys.update_types;
+
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+ ) VALUES (
+ 1 , 1 , 'Test 1' , '' , 1
+ ) , (
+ 2 , 1 , 'Test 3' , '' , 75
+ ) , (
+ 3 , 1 , 'Test 2' , '' , 2
+ );
+ SELECT diag_test_name( 'Update types - Ordering column is updated' );
+ SELECT set_eq(
+ $$ SELECT updtype_id , updtype_ordering FROM sys.update_types $$ ,
+ $$ VALUES ( 1 , 2 ) , ( 3 , 4 ) , ( 2 , 6 ) $$
+ );
+ DELETE FROM sys.update_types;
+
+
+ INSERT INTO test.test VALUES ( 1 ) , ( 2 );
+ INSERT INTO sys.update_types(
+ updtype_id , updtgt_id , updtype_name , updtype_description , updtype_ordering
+ ) VALUES (
+ 1 , 1 , 'Test 1' , '' , 1
+ );
+ SELECT diag_test_name( 'Update types - Type creation adds specific rows for missing items' );
+ SELECT set_eq(
+ $$ SELECT updtgt_id , updtype_id , test_id FROM test.test_updates $$ ,
+ $$ VALUES ( 1 , 1 , 1 ) , ( 1 , 1 , 2 ) $$
+ );
+ SELECT diag_test_name( 'Update types - Type creation adds genertic rows for missing items' );
+ SELECT set_eq(
+ $$ SELECT test_id , update_state , update_last
+ FROM test.test_updates
+ INNER JOIN sys.updates USING ( updtgt_id , updtype_id , update_id ) $$ ,
+ $$ VALUES ( 1 , 'FUTURE'::sys.update_state_type , -1 ) ,
+ ( 2 , 'FUTURE'::sys.update_state_type , '-1' ) $$
+ );
+
+ INSERT INTO test.test VALUES ( 3 );
+ SELECT diag_test_name( 'Update types - New items have automatically inserted update rows' );
+ SELECT set_eq(
+ $$ SELECT test_id , updtype_id , update_state , update_last
+ FROM test.test_updates
+ INNER JOIN sys.updates USING ( updtgt_id , updtype_id , update_id )
+ WHERE test_id = 3 $$ ,
+ $$ VALUES ( 3 , 1 , 'FUTURE'::sys.update_state_type , -1 ) $$
+ );
+
+ DELETE FROM sys.update_types WHERE updtype_id = 1;
+ SELECT diag_test_name( 'Update types - Type deletion cascades to specific rows' );
+ SELECT is_empty( $$ SELECT * FROM test.test_updates $$ );
+ SELECT diag_test_name( 'Update types - Type deletion cascades to generic rows' );
+ SELECT is_empty( $$ SELECT * FROM sys.updates $$ );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
index 72671df..6520694 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/040-empire/010-create-empire.sql
@@ -16,7 +16,7 @@ BEGIN;
SELECT _create_emp_names( 1 , 'testEmp' );
/***** TESTS BEGIN HERE *****/
- SELECT plan( 8 );
+ SELECT plan( 7 );
SELECT emp.create_empire( _get_emp_name( 'testEmp1' ) ,
_get_map_name( 'testPlanet1' ) ,
@@ -57,16 +57,5 @@ BEGIN;
FROM emp.resources
WHERE empire_id = _get_emp_name( 'testEmp1' );
- SELECT diag_test_name( 'emp.create_empire() - Empire update records' );
- SELECT is( _eur.quantity , _utv.quantity)
- FROM (
- SELECT COUNT(*) AS quantity FROM emp.updates
- WHERE empire_id = _get_emp_name( 'testEmp1' )
- ) AS _eur , (
- SELECT COUNT(*) AS quantity
- FROM unnest( enum_range( NULL::update_type ) ) AS _row
- WHERE _row::TEXT LIKE 'EMPIRE_%'
- ) AS _utv;
-
SELECT * FROM finish( );
ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql
new file mode 100644
index 0000000..85e5731
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/010-start-tick.sql
@@ -0,0 +1,53 @@
+/*
+ * Test the sys.start_tick() function
+ */
+BEGIN;
+ -- Delete all registered update types and targets
+ DELETE FROM sys.update_types;
+ DELETE FROM sys.update_targets;
+
+ -- Create a new update target / type which will be used in tests
+ CREATE SCHEMA test
+ CREATE TABLE test( test_id INT NOT NULL PRIMARY KEY );
+ INSERT INTO sys.update_targets VALUES ( 1 , 'Test' , 'test' , 'test' );
+ INSERT INTO sys.update_types VALUES ( 1 , 1 , 'Test' , 2 , '' , NULL , NULL );
+
+ -- Insert a few update rows
+ INSERT INTO test.test VALUES ( 1 ) , ( 2 ) , ( 3 );
+
+ -- ***** TESTS BEGIN HERE *****
+ SELECT plan( 5 );
+
+ UPDATE sys.status SET maintenance_start = now( ) , current_tick = NULL;
+ SELECT diag_test_name( 'sys.start_tick() - Ticks do not start if maintenance mode is active' );
+ SELECT ok( sys.start_tick( ) IS NULL );
+
+ UPDATE sys.status SET maintenance_start = NULL , current_tick = next_tick;
+ SELECT diag_test_name( 'sys.start_tick() - Ticks do not start if current_tick is set' );
+ SELECT ok( sys.start_tick( ) IS NULL );
+
+ UPDATE sys.status SET current_tick = NULL , next_tick = 2;
+ UPDATE sys.updates SET update_state = 'PROCESSED' , update_last = -1;
+ UPDATE sys.updates su SET update_last = 514
+ FROM test.test_updates tu
+ WHERE su.update_id = tu.update_id
+ AND tu.test_id = 3;
+ SELECT diag_test_name( 'sys.start_tick() - Returns next tick identifier when ticks can start' );
+ SELECT is( sys.start_tick( ) , 2::BIGINT );
+ SELECT diag_test_name( 'sys.start_tick() - System status is updated' );
+ SELECT set_eq(
+ $$ SELECT current_tick , next_tick FROM sys.status $$ ,
+ $$ VALUES ( 2 , 3 ) $$
+ );
+ SELECT diag_test_name( 'sys.start_tick() - Update rows are marked for processing when necessary' );
+ SELECT set_eq(
+ $$ SELECT test_id
+ FROM test.test_updates
+ INNER JOIN sys.updates
+ USING ( update_id , updtgt_id , updtype_id )
+ WHERE update_state = 'FUTURE' AND update_last = 2 $$ ,
+ $$ VALUES ( 1 ) , ( 2 ) $$
+ );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
new file mode 100644
index 0000000..cc5eaf9
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
@@ -0,0 +1,60 @@
+/*
+ * Tests of the sys.check_stuck_tick() function
+ */
+BEGIN;
+ -- Delete all registered update types and targets
+ DELETE FROM sys.update_types;
+ DELETE FROM sys.update_targets;
+
+ -- Create a new update target / type which will be used in tests
+ CREATE SCHEMA test
+ CREATE TABLE test( test_id INT NOT NULL PRIMARY KEY );
+ INSERT INTO sys.update_targets VALUES ( 1 , 'Test' , 'test' , 'test' );
+ INSERT INTO sys.update_types VALUES ( 1 , 1 , 'Test' , 2 , '' , NULL , NULL );
+
+ -- Insert a few update rows
+ INSERT INTO test.test VALUES ( 1 ) , ( 2 ) , ( 3 );
+
+ -- Replace sys.end_tick() with a function that inserts into some table
+ CREATE TABLE end_tick_calls( tick_id BIGINT NOT NULL );
+ CREATE OR REPLACE FUNCTION sys.end_tick( tick_id BIGINT )
+ RETURNS VOID LANGUAGE SQL
+ AS 'INSERT INTO end_tick_calls VALUES( $1 );';
+
+ -- ***** TESTS BEGIN HERE *****
+ SELECT plan( 8 );
+
+ UPDATE sys.status SET maintenance_start = NULL , current_tick = NULL;
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Returns NULL if no tick is in progress' );
+ SELECT ok( sys.check_stuck_tick() IS NULL );
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Does not call sys.end_tick() if no tick is in progress' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+ DELETE FROM end_tick_calls;
+
+ UPDATE sys.status SET maintenance_start = now( ) , current_tick = 2;
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Returns NULL if maintenance mode is enabled' );
+ SELECT ok( sys.check_stuck_tick() IS NULL );
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Does not call sys.end_tick() if maintenance mode is enabled' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+ DELETE FROM end_tick_calls;
+
+ UPDATE sys.status SET maintenance_start = NULL , current_tick = 2;
+ UPDATE sys.updates SET update_state = 'PROCESSED' , update_last = 2;
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Returns NULL if a tick is in progress but there are no updates left' );
+ SELECT ok( sys.check_stuck_tick() IS NULL );
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Calls sys.end_tick() if a tick is in progress but there are no updates left' );
+ SELECT set_eq(
+ $$ SELECT * FROM end_tick_calls $$ ,
+ $$ VALUES ( 2 ) $$
+ );
+ DELETE FROM end_tick_calls;
+
+ UPDATE sys.status SET maintenance_start = NULL , current_tick = 2;
+ UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 2;
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Returns tick identifier if a tick is in progress and there are updates left' );
+ SELECT is( sys.check_stuck_tick() , 2::BIGINT );
+ SELECT diag_test_name( 'sys.check_stuck_tick() - Does not call sys.end_tick() if a tick is in progress and there are updates left' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql
new file mode 100644
index 0000000..ab25ff1
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/000-updates-ctrl/030-process-updates.sql
@@ -0,0 +1,179 @@
+/*
+ * Tests for the sys.process_updates() function
+ */
+BEGIN;
+ -- Delete all registered update types and targets
+ DELETE FROM sys.update_types;
+ DELETE FROM sys.update_targets;
+
+ -- Create a new update target / type which will be used in tests
+ CREATE SCHEMA test
+ CREATE TABLE test( test_id INT NOT NULL PRIMARY KEY );
+ INSERT INTO sys.update_targets VALUES ( 1 , 'Test' , 'test' , 'test' );
+ INSERT INTO sys.update_types VALUES ( 1 , 1 , 'Test 1' , 2 , '' , NULL , NULL );
+ INSERT INTO sys.update_types VALUES ( 2 , 1 , 'Test 2' , 4 , '' , NULL , NULL );
+
+ -- Replace sys.end_tick() with a function that inserts into some table
+ CREATE TABLE end_tick_calls( tick_id BIGINT NOT NULL );
+ CREATE OR REPLACE FUNCTION sys.end_tick( tick_id BIGINT )
+ RETURNS VOID LANGUAGE SQL
+ AS 'INSERT INTO end_tick_calls VALUES( $1 );';
+
+ -- Create a sys.process_dummy_update() function that inserts into some other table
+ CREATE TABLE process_update_calls( tick_id BIGINT NOT NULL );
+ CREATE OR REPLACE FUNCTION sys.process_dummy_update( tick_id BIGINT )
+ RETURNS VOID LANGUAGE SQL
+ AS 'INSERT INTO process_update_calls VALUES( $1 );';
+
+ -- Set the general batch size to 1
+ SELECT sys.uoc_constant( 'game.batchSize' , '(test)' , 'Game updates' , 1 );
+
+ -- Insert a few entries in the test table
+ INSERT INTO test.test
+ SELECT * FROM generate_series( 1 , 10 );
+
+ -- ***** TESTS BEGIN HERE *****
+ SELECT plan( 21 );
+
+ /*
+ * Case:
+ * Updates have not been processed yet,
+ *
+ * Expected results:
+ * 1) New updates of the first type have been marked for processing,
+ * 2) The quantity of updates marked for processing corresponds to game.batchSize
+ */
+ UPDATE sys.updates
+ SET update_state = 'FUTURE' , update_last = 1;
+ SELECT diag_test_name( 'sys.process_updates() - General batch size - Return value' );
+ SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 1'::TEXT ) );
+ SELECT diag_test_name( 'sys.process_updates() - General batch size - Update type' );
+ SELECT set_eq(
+ $$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state = 'PROCESSING'; $$ ,
+ $$ VALUES ( 1 ) $$
+ );
+ SELECT diag_test_name( 'sys.process_updates() - General batch size - Update count' );
+ SELECT is( COUNT(*)::INT , 1 ) FROM sys.updates WHERE update_state = 'PROCESSING';
+ SELECT diag_test_name( 'sys.process_updates() - General batch size - sys.end_tick() not called' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+ DELETE FROM end_tick_calls;
+
+ /*
+ * Case:
+ * Updates have not been processed yet,
+ * Update type has a specific batch size,
+ *
+ * Expected results:
+ * 1) New updates have been marked for processing,
+ * 2) The quantity of updates marked for processing corresponds to the specific batch size
+ * 3) sys.end_tick() has not been called
+ */
+ UPDATE sys.update_types SET updtype_batch_size = 2 WHERE updtype_id = 1;
+ UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+ SELECT diag_test_name( 'sys.process_updates() - Specific batch size - Return value' );
+ SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 1'::TEXT ) );
+ SELECT diag_test_name( 'sys.process_updates() - Specific batch size - Update type' );
+ SELECT set_eq(
+ $$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state = 'PROCESSING'; $$ ,
+ $$ VALUES ( 1 ) $$
+ );
+ SELECT diag_test_name( 'sys.process_updates() - Specific batch size - Update count' );
+ SELECT is( COUNT(*)::INT , 2 ) FROM sys.updates WHERE update_state = 'PROCESSING';
+ SELECT diag_test_name( 'sys.process_updates() - Specific batch size - sys.end_tick() not called' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+ DELETE FROM end_tick_calls;
+
+ /*
+ * Case:
+ * Some but not all updates of the first type have been processed,
+ * Batch size is greater than the quantity of updates left to process for the type.
+ *
+ * Expected results:
+ * 1) All updates of the first type are marked either for processing or as processed.
+ * 2) sys.end_tick() has not been called
+ */
+ UPDATE sys.update_types SET updtype_batch_size = 2 WHERE updtype_id = 1;
+ UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+ UPDATE sys.updates
+ SET update_state = 'PROCESSED'
+ WHERE update_id IN (
+ SELECT update_id FROM sys.updates WHERE updtype_id = 1 LIMIT 9
+ );
+ UPDATE sys.update_types SET updtype_batch_size = 2 WHERE updtype_id = 1;
+ SELECT diag_test_name( 'sys.process_updates() - End of type updates - Return value' );
+ SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 1'::TEXT ) );
+ SELECT diag_test_name( 'sys.process_updates() - End of type updates - Update type' );
+ SELECT set_eq(
+ $$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state <> 'FUTURE'; $$ ,
+ $$ VALUES ( 1 ) $$
+ );
+ SELECT diag_test_name( 'sys.process_updates() - End of type updates - Update count' );
+ SELECT is( COUNT(*)::INT , 10 ) FROM sys.updates WHERE update_state <> 'FUTURE';
+ SELECT diag_test_name( 'sys.process_updates() - End of type updates - sys.end_tick() not called' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+ DELETE FROM end_tick_calls;
+
+ /*
+ * Case:
+ * All updates of the first type have been processed.
+ * No updates of the second type have been processed.
+ *
+ * Expected results:
+ * 1) Some updates from the second type have been marked for processing.
+ * 2) sys.end_tick() has not been called
+ */
+ UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+ UPDATE sys.updates
+ SET update_state = 'PROCESSED'
+ WHERE updtype_id = 1;
+ SELECT diag_test_name( 'sys.process_updates() - Update type transition - Return value' );
+ SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , 'Test 2'::TEXT ) );
+ SELECT diag_test_name( 'sys.process_updates() - Update type transition - Update type' );
+ SELECT set_eq(
+ $$ SELECT DISTINCT updtype_id FROM sys.updates WHERE update_state = 'PROCESSING'; $$ ,
+ $$ VALUES ( 2 ) $$
+ );
+ SELECT diag_test_name( 'sys.process_updates() - Update type transition - Update count' );
+ SELECT is( COUNT(*)::INT , 1 ) FROM sys.updates WHERE update_state = 'PROCESSING';
+ SELECT diag_test_name( 'sys.process_updates() - Update type transition - sys.end_tick() not called' );
+ SELECT is_empty( $$ SELECT * FROM end_tick_calls $$ );
+ DELETE FROM end_tick_calls;
+
+ /*
+ * Case:
+ * There no updates to process
+ * Updates are marked as 'PROCESSING'
+ *
+ * Expected results:
+ * 1) _has_more return value is FALSE,
+ * 2) All updates are marked as 'PROCESSED'
+ * 3) sys.end_tick() has been called.
+ */
+ UPDATE sys.updates SET update_state = 'PROCESSING' , update_last = 1;
+ SELECT diag_test_name( 'sys.process_updates() - No updates left - Return value' );
+ SELECT ok( sys.process_updates( 1 ) = ROW( FALSE , NULL::TEXT ) );
+ SELECT diag_test_name( 'sys.process_updates() - No updates left - All updates processed' );
+ SELECT is_empty( $$ SELECT * FROM sys.updates WHERE update_state <> 'PROCESSED' $$ );
+ SELECT diag_test_name( 'sys.process_updates() - No updates left - sys.end_tick() called' );
+ SELECT set_eq( $$ SELECT * FROM end_tick_calls $$ , $$ VALUES ( 1 ) $$ );
+ DELETE FROM end_tick_calls;
+
+ /*
+ * Case:
+ * The update type has an in-base processing function
+ *
+ * Expected result:
+ * 1) _process_externally return value is NULL
+ * 2) The processing function has been called.
+ */
+ UPDATE sys.updates SET update_state = 'FUTURE' , update_last = 1;
+ UPDATE sys.update_types
+ SET updtype_batch_size = NULL , updtype_proc_name = 'process_dummy_update'
+ WHERE updtype_id = 1;
+ SELECT diag_test_name( 'sys.process_updates() - In-base processing - Return value' );
+ SELECT ok( sys.process_updates( 1 ) = ROW( TRUE , NULL::TEXT ) );
+ SELECT diag_test_name( 'sys.process_updates() - In-base processing - Processing function called' );
+ SELECT set_eq( $$ SELECT * FROM process_update_calls $$ , $$ VALUES ( 1 ) $$ );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql
new file mode 100644
index 0000000..595b988
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/003-core-update-targets/010-presence.sql
@@ -0,0 +1,17 @@
+/*
+ * Check that core update targets have been defined correctly
+ */
+BEGIN;
+ SELECT plan( 3 );
+
+ SELECT diag_test_name( 'Update targets - Definitions present' );
+ SELECT is( COUNT(*)::INT , 2 ) FROM sys.update_targets;
+
+ SELECT diag_test_name( 'Update targets - verse.planets_updates exists' );
+ SELECT has_table( 'verse' , 'planets_updates' , 'Missing planets update table' );
+
+ SELECT diag_test_name( 'Update targets - emp.empires_updates exists' );
+ SELECT has_table( 'emp' , 'empires_updates' , 'Missing empires update table' );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
index 2b4f57a..5e670c7 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/015-empire-resources/010-process-empire-resources-updates.sql
@@ -2,6 +2,12 @@
* Test the sys.process_empire_resources_updates() function
*/
BEGIN;
+ /* Start by deleting all update types except for the one
+ * we're interested in.
+ */
+ DELETE FROM sys.update_types
+ WHERE updtype_name <> 'EmpireResources';
+
/* We need:
* - three types of resources,
* - two empire with resources records, and the corresponding update
@@ -35,14 +41,15 @@ BEGIN;
/* Make sure update 0 is set to be processed for testEmp1 */
UPDATE sys.updates
- SET status = 'PROCESSING' , last_tick = 0;
+ SET update_state = 'PROCESSING' , update_last = 0;
/* Create testEmp2 empire, make sure it will not be processed */
SELECT emp.create_empire( _get_emp_name( 'testEmp2' ) , _get_map_name( 'testPlanet2' ) , 1 );
UPDATE sys.updates _su
- SET status = 'PROCESSED' , last_tick = 0
- FROM emp.updates _eu
- WHERE _eu.update_id = _su.id AND _eu.empire_id = _get_emp_name( 'testEmp2' );
+ SET update_state = 'PROCESSED' , update_last = 0
+ FROM emp.empires_updates _eu
+ WHERE _eu.update_id = _su.update_id
+ AND _eu.name_id = _get_emp_name( 'testEmp2' );
/****** TESTS BEGIN HERE ******/
SELECT plan( 19 );
diff --git a/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
index 54385b4..72c4188 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
@@ -2,6 +2,11 @@
* Test for the sys.process_planet_res_regen_updates() function
*/
BEGIN;
+ /* Start by deleting all update types except for the one
+ * we're interested in.
+ */
+ DELETE FROM sys.update_types
+ WHERE updtype_name <> 'ResourceRegeneration';
/* We need to create a natural resource and a pair of planets */
\i utils/strings.sql
@@ -34,23 +39,19 @@ BEGIN;
0.5 , 0.5
);
- /* Insert update records for the planets. Planets 1 and 2 will be processed,
+ /* Modify update records for the planets. Planets 1 and 2 will be processed,
* planet testPlanet3 will not.
*/
- INSERT INTO sys.updates ( id , gu_type , status , last_tick )
- VALUES ( 1 , 'PLANET_RES_REGEN' , 'PROCESSING' , 0 );
- INSERT INTO verse.updates ( update_id , planet_id )
- VALUES ( 1 , _get_map_name( 'testPlanet1' ) );
-
- INSERT INTO sys.updates ( id , gu_type , status , last_tick )
- VALUES ( 2 , 'PLANET_RES_REGEN' , 'PROCESSING' , 0 );
- INSERT INTO verse.updates ( update_id , planet_id )
- VALUES ( 2 , _get_map_name( 'testPlanet2' ) );
-
- INSERT INTO sys.updates ( id , gu_type , status , last_tick )
- VALUES ( 3 , 'PLANET_RES_REGEN' , 'PROCESSED' , 0 );
- INSERT INTO verse.updates ( update_id , planet_id )
- VALUES ( 3 , _get_map_name( 'testPlanet3' ) );
+ UPDATE sys.updates su
+ SET update_state = 'PROCESSING' , update_last = 0
+ FROM verse.planets_updates vu
+ WHERE vu.update_id = su.update_id
+ AND vu.name_id <> _get_map_name( 'testPlanet3' );
+ UPDATE sys.updates su
+ SET update_state = 'PROCESSED' , update_last = 0
+ FROM verse.planets_updates vu
+ WHERE vu.update_id = su.update_id
+ AND vu.name_id = _get_map_name( 'testPlanet3' );
/* Helper function that gets a provider's current quantity */
CREATE FUNCTION _get_quantity( _planet TEXT )
@@ -65,7 +66,7 @@ BEGIN;
SELECT plan( 6 );
- SELECT sys.process_planet_res_regen_updates( 10 );
+ SELECT sys.process_res_regen_updates( 10 );
SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (1/3)' );
SELECT ok( _get_quantity( 'testPlanet1' ) = 50 );
SELECT diag_test_name( 'PLANET_RES_REGEN update - Wrong update identifier (2/3)' );
@@ -74,7 +75,7 @@ BEGIN;
SELECT ok( _get_quantity( 'testPlanet3' ) = 50 );
- SELECT sys.process_planet_res_regen_updates( 0 );
+ SELECT sys.process_res_regen_updates( 0 );
SELECT diag_test_name( 'PLANET_RES_REGEN update - Processed planet with quantity < max.' );
SELECT ok( _get_quantity( 'testPlanet1' ) > 50 );
SELECT diag_test_name( 'PLANET_RES_REGEN update - Processed planet with quantity = max.' );
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql
new file mode 100644
index 0000000..70343c3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/010-update-targets.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on sys.update_targets
+ */
+BEGIN;
+ SELECT plan( 4 );
+
+ SELECT diag_test_name( 'sys.update_targets - No INSERT privilege' );
+ SELECT throws_ok(
+ $$ INSERT INTO sys.update_targets DEFAULT VALUES; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.update_targets - No UPDATE privilege' );
+ SELECT throws_ok(
+ $$ UPDATE sys.update_targets SET updtgt_id = 42; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.update_targets - No SELECT privilege' );
+ SELECT throws_ok(
+ $$ SELECT * FROM sys.update_targets; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.update_targets - No DELETE privilege' );
+ SELECT throws_ok(
+ $$ DELETE FROM sys.update_targets; $$ ,
+ 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql
new file mode 100644
index 0000000..fc33d56
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/020-update-types.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on sys.update_types
+ */
+BEGIN;
+ SELECT plan( 4 );
+
+ SELECT diag_test_name( 'sys.update_types - No INSERT privilege' );
+ SELECT throws_ok(
+ $$ INSERT INTO sys.update_types DEFAULT VALUES; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.update_types - No UPDATE privilege' );
+ SELECT throws_ok(
+ $$ UPDATE sys.update_types SET updtgt_id = 42; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.update_types - No SELECT privilege' );
+ SELECT throws_ok(
+ $$ SELECT * FROM sys.update_types; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.update_types - No DELETE privilege' );
+ SELECT throws_ok(
+ $$ DELETE FROM sys.update_types; $$ ,
+ 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql
new file mode 100644
index 0000000..cc86cb3
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/145-updates/030-updates.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on sys.updates
+ */
+BEGIN;
+ SELECT plan( 4 );
+
+ SELECT diag_test_name( 'sys.updates - No INSERT privilege' );
+ SELECT throws_ok(
+ $$ INSERT INTO sys.updates DEFAULT VALUES; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.updates - No UPDATE privilege' );
+ SELECT throws_ok(
+ $$ UPDATE sys.updates SET updtgt_id = 42; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.updates - No SELECT privilege' );
+ SELECT throws_ok(
+ $$ SELECT * FROM sys.updates; $$ ,
+ 42501 );
+
+ SELECT diag_test_name( 'sys.updates - No DELETE privilege' );
+ SELECT throws_ok(
+ $$ DELETE FROM sys.updates; $$ ,
+ 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql
new file mode 100644
index 0000000..ef222fc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/010-get-table-pkey.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.get_table_pkey()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.get_table_pkey() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.get_table_pkey( 'a' , 'b' ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql
new file mode 100644
index 0000000..dae5ca2
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/020-tgf-speupd-before-insert.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_speupd_before_insert()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_speupd_before_insert() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_speupd_before_insert( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql
new file mode 100644
index 0000000..431a387
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/030-tgf-speupd-after-delete.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_speupd_after_delete()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_speupd_after_delete() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_speupd_after_delete( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql
new file mode 100644
index 0000000..2b22f82
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/040-insert-missing-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.insert_missing_updates()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.insert_missing_updates() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.insert_missing_updates( 1 ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql
new file mode 100644
index 0000000..26f9c10
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/050-tgf-insert-missing-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_insert_missing_updates()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_insert_missing_updates() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_insert_missing_updates( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql
new file mode 100644
index 0000000..5fce32c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/060-tgf-updtype-after-insert-row.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtype_after_insert_row()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_updtype_after_insert_row() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_updtype_after_insert_row( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql
new file mode 100644
index 0000000..064dbdc
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/070-tgf-check-update-type-proc.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_check_update_type_proc()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_check_update_type_proc() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_check_update_type_proc( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql
new file mode 100644
index 0000000..bb27646
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/080-tgf-reorder-update-types.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_reorder_update_types()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_reorder_update_types() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_reorder_update_types( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql
new file mode 100644
index 0000000..5b6ea64
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/090-tgf-updtgt-before-insert.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtgt_before_insert()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_updtgt_before_insert() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_updtgt_before_insert( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql
new file mode 100644
index 0000000..6da4cfa
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/100-tgf-updtgt-after-insert.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtgt_after_insert()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_updtgt_after_insert() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_updtgt_after_insert( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql
new file mode 100644
index 0000000..0d1c554
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/110-tgf-updtgt-after-delete.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.tgf_updtgt_after_delete()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.tgf_updtgt_after_delete() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.tgf_updtgt_after_delete( ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql
new file mode 100644
index 0000000..f25b59e
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/003-updates/120-register-update-type.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.register_update_type()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.register_update_type() - No EXECUTE privilege' );
+ SELECT throws_ok( $$ SELECT sys.register_update_type( '' , '' , '' , 'x' ) $$ , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql
new file mode 100644
index 0000000..cb763fb
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/010-start-tick.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.start_tick()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.start_tick() - EXECUTE privilege' );
+ SELECT lives_ok( 'SELECT sys.start_tick( )' );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
new file mode 100644
index 0000000..5799b95
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/020-check-stuck-tick.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.check_stuck_tick()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.check_stuck_tick() - EXECUTE privilege' );
+ SELECT lives_ok( 'SELECT sys.check_stuck_tick( )' );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql
new file mode 100644
index 0000000..5416d6c
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/000-updates-ctrl/030-process-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_updates()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.process_updates() - EXECUTE privilege' );
+ SELECT lives_ok( 'SELECT sys.process_updates( 1 )' );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
deleted file mode 100644
index b08eb50..0000000
--- a/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-planet-res-regen-updates.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Test privileges on sys.process_planet_res_regen_updates()
- */
-BEGIN;
- SELECT plan( 1 );
-
- SELECT diag_test_name( 'sys.process_planet_res_regen_updates() - Privileges' );
- SELECT throws_ok( 'SELECT sys.process_planet_res_regen_updates( 1 )' , 42501 );
-
- SELECT * FROM finish( );
-ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql
new file mode 100644
index 0000000..cb4bcd6
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/050-updates/105-planet-resource-regeneration/010-process-res-regen-updates.sql
@@ -0,0 +1,11 @@
+/*
+ * Test privileges on sys.process_res_regen_updates()
+ */
+BEGIN;
+ SELECT plan( 1 );
+
+ SELECT diag_test_name( 'sys.process_res_regen_updates() - Privileges' );
+ SELECT throws_ok( 'SELECT sys.process_res_regen_updates( 1 )' , 42501 );
+
+ SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
index 97df95f..57d62d3 100644
--- a/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
+++ b/legacyworlds-server-data/db-structure/tests/utils/common-setup/setup-gu-pmc-get-data-test.sql
@@ -21,13 +21,13 @@
* - Finally, we need a planet that matches the criterias but isn't
* scheduled for an update at the time.
*
- * FIXME: for now, locking is NOT tested. I simply have no idea how to do
- * it, at least not without creating an extra database and that
- * kind of stuff.
- *
* FIXME: cannot test where the happiness column comes from until values
* all use DOUBLE PRECISION.
*/
+
+DELETE FROM sys.update_types
+ WHERE updtype_name <> 'PlanetMining';
+
\i utils/strings.sql
\i utils/resources.sql
\i utils/accounts.sql
@@ -41,17 +41,7 @@ SELECT _create_emp_names( 4 , 'empire' );
INSERT INTO emp.empires ( name_id , cash )
SELECT id , 0 FROM naming.empire_names;
-/* Planet #1 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
- VALUES ( 1 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
- VALUES ( 1 , _get_map_name( 'planet1' ) );
-
/* Planet #2 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
- VALUES ( 2 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
- VALUES ( 2 , _get_map_name( 'planet2' ) );
INSERT INTO verse.resource_providers (
planet_id , resource_name_id , resprov_quantity_max ,
resprov_quantity , resprov_difficulty , resprov_recovery
@@ -64,10 +54,6 @@ INSERT INTO verse.resource_providers (
);
/* Planet #3 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
- VALUES ( 3 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
- VALUES ( 3 , _get_map_name( 'planet3' ) );
INSERT INTO verse.resource_providers (
planet_id , resource_name_id , resprov_quantity_max ,
resprov_quantity , resprov_difficulty , resprov_recovery
@@ -90,10 +76,6 @@ INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
);
/* Planet #4 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
- VALUES ( 4 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
- VALUES ( 4 , _get_map_name( 'planet4' ) );
INSERT INTO verse.resource_providers (
planet_id , resource_name_id , resprov_quantity_max ,
resprov_quantity , resprov_difficulty , resprov_recovery
@@ -123,10 +105,6 @@ INSERT INTO emp.planet_mining_settings(
);
/* Planet #5 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
- VALUES ( 5 , 'PLANET_MINING' , 'PROCESSING' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
- VALUES ( 5 , _get_map_name( 'planet5' ) );
INSERT INTO verse.planet_happiness ( planet_id , current , target )
VALUES ( _get_map_name( 'planet5' ) , 0.5 , 0.5 );
INSERT INTO emp.planets ( empire_id , planet_id )
@@ -139,10 +117,6 @@ INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
);
/* Planet #6 */
-INSERT INTO sys.updates( id , gu_type , status , last_tick )
- VALUES ( 6 , 'PLANET_MINING' , 'PROCESSED' , 0 );
-INSERT INTO verse.updates( update_id , planet_id )
- VALUES ( 6 , _get_map_name( 'planet6' ) );
INSERT INTO verse.resource_providers (
planet_id , resource_name_id , resprov_quantity_max ,
resprov_quantity , resprov_difficulty , resprov_recovery
@@ -168,3 +142,16 @@ INSERT INTO emp.mining_settings( empire_id , resource_name_id , empmset_weight )
INSERT INTO verse.planet_resources ( planet_id , resource_name_id , pres_income , pres_upkeep )
SELECT p.name_id , r.resource_name_id , 42 , 0
FROM verse.planets p CROSS JOIN defs.resources r;
+
+/* Modify update records for the planets. Planet 6 is not to be processed.
+ */
+UPDATE sys.updates su
+ SET update_state = 'PROCESSING' , update_last = 0
+ FROM verse.planets_updates vu
+ WHERE vu.update_id = su.update_id
+ AND vu.name_id <> _get_map_name( 'planet6' );
+UPDATE sys.updates su
+ SET update_state = 'PROCESSED' , update_last = 0
+ FROM verse.planets_updates vu
+ WHERE vu.update_id = su.update_id
+ AND vu.name_id = _get_map_name( 'planet6' );
diff --git a/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java
new file mode 100644
index 0000000..5388528
--- /dev/null
+++ b/legacyworlds-server-data/src/main/java/com/deepclone/lw/sqld/sys/GameUpdateResult.java
@@ -0,0 +1,82 @@
+package com.deepclone.lw.sqld.sys;
+
+
+/**
+ * The result of a game update
+ *
+ *
+ * This class is used to transmit the results of the game updates processor. The results may
+ * indicate:
+ *
+ * - a finished game update cycle that needs no further processing,
+ *
- an in-progress update cycle for which further in-base processing is needed,
+ *
- an in-progress update cycle where some processing must be done in the server.
+ *
+ *
+ * @author E. Benoît
+ */
+public class GameUpdateResult
+{
+
+ /** Whether the update cycle is finished */
+ private final boolean finished;
+
+ /**
+ * Name of the type of processing that needs to be run on the server, or null
if
+ * the update is finished or was processed in-base
+ */
+ private final String localExecution;
+
+
+ /**
+ * Initialise a "finished" game update result
+ *
+ *
+ * This constructor will set {@link #finished} to true
and {@link #localExecution}
+ * to null
.
+ */
+ public GameUpdateResult( )
+ {
+ this.finished = true;
+ this.localExecution = null;
+ }
+
+
+ /**
+ * Initialise an "incomplete" game update result
+ *
+ *
+ * This constructor will set {@link #finished} to false
and {@link #localExecution}
+ * to the parameter's value.
+ *
+ * @param localExecution
+ * the name of the type of processing to be executed on the server, or
+ * null
if the update was processed in-base.
+ */
+ public GameUpdateResult( String localExecution )
+ {
+ this.finished = false;
+ this.localExecution = localExecution;
+ }
+
+
+ /**
+ * @return true
if the update cycle was completed or false
if further
+ * processing is required.
+ */
+ public boolean isFinished( )
+ {
+ return this.finished;
+ }
+
+
+ /**
+ * @return Name of the type of processing that needs to be run on the server, or
+ * null
if the update is finished or was processed in-base
+ */
+ public String getLocalExecution( )
+ {
+ return this.localExecution;
+ }
+
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
deleted file mode 100644
index 86736b4..0000000
--- a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/UpdatesDAO.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.deepclone.lw.interfaces.game;
-
-
-public interface UpdatesDAO
-{
- public boolean processUpdates( long tickId );
-}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java
new file mode 100644
index 0000000..9008c4f
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/GameUpdateProcessor.java
@@ -0,0 +1,75 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Game update processor
+ *
+ *
+ * The game update processor allows game update cycles to be executed. It also provides the ability
+ * to terminated an existing update cycle.
+ *
+ *
+ * The game update processor includes a locking mechanism which can be used to prevent two
+ * components (for example the game update ticker task and the administration interface) from
+ * executing game updates simultaneously.
+ *
+ * @author E. Benoît
+ */
+public interface GameUpdateProcessor
+{
+
+ /**
+ * Try locking the game update processor
+ *
+ *
+ * Test and set the processor's lock. This lock is not re-entrant and should only be called once
+ * per locking thread.
+ *
+ * @return true
if the lock was acquired, false
it some other
+ * component had already locked the processor.
+ */
+ public boolean tryLock( );
+
+
+ /**
+ * Unlock the game update processor
+ *
+ *
+ * Reset the processor's lock. This method should be called from the thread which actually
+ * locked the processor in the first place, although that will not be checked.
+ *
+ * @throws IllegalStateException
+ * if the processor is not locked.
+ */
+ public void unlock( )
+ throws IllegalStateException;
+
+
+ /**
+ * End the previous update cycle
+ *
+ *
+ * Check if an update cycle had already started. If such is the case, execute all remaining
+ * update batches.
+ *
+ * @return true
if an update cycle was processed or if the system is under
+ * maintenance, false
otherwise.
+ */
+ public boolean endPreviousCycle( );
+
+
+ /**
+ * Execute a full update cycle
+ *
+ *
+ * Check for an update cycle that was already started. If one exists, finish it. Otherwise,
+ * start a new cycle and execute all batches in separate transactions.
+ *
+ *
+ * Update cycles will not be processed if the system is under maintenance. In addition, if the
+ * system enters maintenance mode while updates are being processed, the processing will stop
+ * after the current batch.
+ */
+ public void executeUpdateCycle( );
+
+}
\ No newline at end of file
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java
new file mode 100644
index 0000000..a295193
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdateBatchProcessor.java
@@ -0,0 +1,42 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+/**
+ * Update batch processor interface
+ *
+ *
+ * This interface must be implemented by all components which provide support for server-side game
+ * updates processing. The components will be gathered automatically when the server initialises.
+ *
+ * @author E. Benoît
+ *
+ */
+public interface UpdateBatchProcessor
+{
+
+ /**
+ * Obtain the component's supported update type
+ *
+ *
+ * This method must be implemented to return a string which corresponds to the name of the
+ * update type in the database's sys.update_types
registry table.
+ *
+ * @return the update type supported by the component
+ */
+ public String getUpdateType( );
+
+
+ /**
+ * Process a batch of updates
+ *
+ *
+ * This method is called by the main update processor in the transaction during which items from
+ * the batch will have been marked for processing. It should process the update, but it must not
+ * modify the update rows in the sys.updates
table.
+ *
+ * @param tickId
+ * the identifier of the current game update cycle
+ */
+ public void processBatch( long tickId );
+
+}
diff --git a/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
new file mode 100644
index 0000000..7c48c40
--- /dev/null
+++ b/legacyworlds-server-interfaces/src/main/java/com/deepclone/lw/interfaces/game/updates/UpdatesDAO.java
@@ -0,0 +1,34 @@
+package com.deepclone.lw.interfaces.game.updates;
+
+
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+
+
+
+/**
+ * Game updates data access
+ *
+ *
+ * This interface allows access to the data and stored procedure which constitute the game update
+ * sub-system.
+ *
+ * @author E. Benoît
+ */
+public interface UpdatesDAO
+{
+
+ /**
+ * Run a batch of game updates
+ *
+ *
+ * Execute a batch of game updates for the current game update cycle. The value returned by this
+ * method determines whether the caller should call the method again, and if so whether it needs
+ * to do some in-server processing.
+ *
+ * @param tickId
+ * the identifier of the current game update cycle
+ *
+ * @return the results of the game update processor
+ */
+ public GameUpdateResult processUpdates( long tickId );
+}
diff --git a/legacyworlds-server-main/pom.xml b/legacyworlds-server-main/pom.xml
index 30a4788..1e4af1b 100644
--- a/legacyworlds-server-main/pom.xml
+++ b/legacyworlds-server-main/pom.xml
@@ -50,6 +50,10 @@
legacyworlds-server-beans-system
com.deepclone.lw
+
+ legacyworlds-server-beans-updates
+ com.deepclone.lw
+
legacyworlds-server-beans-user
com.deepclone.lw
diff --git a/legacyworlds-server-main/src/main/resources/lw-server.xml b/legacyworlds-server-main/src/main/resources/lw-server.xml
index 40e41af..cf7c8ea 100644
--- a/legacyworlds-server-main/src/main/resources/lw-server.xml
+++ b/legacyworlds-server-main/src/main/resources/lw-server.xml
@@ -24,4 +24,10 @@
+
+
+
+
+
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java
new file mode 100644
index 0000000..bc5c718
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/DummyBatchProcessor.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * A dummy batch processor used in game update tests
+ *
+ * @author E. Benoît
+ */
+class DummyBatchProcessor
+ implements UpdateBatchProcessor
+{
+ /** The name that will be returned by {@link #getUpdateType()} */
+ private final String updateType;
+
+ /** Ticks for which the {@link #processBatch(long)} was called */
+ private final HashSet< Long > ticks = new HashSet< Long >( );
+
+
+ /**
+ * Set the update type
+ *
+ * @param updateType
+ * the update type
+ */
+ public DummyBatchProcessor( String updateType )
+ {
+ this.updateType = updateType;
+ }
+
+
+ /**
+ * @return whatever type name was set when the instance was created
+ */
+ @Override
+ public String getUpdateType( )
+ {
+ return this.updateType;
+ }
+
+
+ /** Adds the specified tick identifier to the set of ticks */
+ @Override
+ public void processBatch( long tickId )
+ {
+ this.ticks.add( tickId );
+ }
+
+
+ /** @return the set of ticks for which {@link #processBatch(long)} was called */
+ Collection< Long > getTicks( )
+ {
+ return this.ticks;
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java
new file mode 100644
index 0000000..d719661
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockGameUpdateProcessor.java
@@ -0,0 +1,91 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
+
+
+
+/**
+ * A mock game update processor
+ *
+ *
+ * This mock component keeps track of which of its methods have been called. It can also simulate
+ * runtime errors during tick processing.
+ *
+ * @author E. Benoît
+ *
+ */
+public class MockGameUpdateProcessor
+ implements GameUpdateProcessor
+{
+ private boolean tryLockCalled = false;
+ private boolean unlockCalled = false;
+ private boolean endCycleCalled = false;
+ private boolean executeCalled = false;
+
+ private boolean failExecute = false;
+
+
+ public boolean wasTryLockCalled( )
+ {
+ return this.tryLockCalled;
+ }
+
+
+ public boolean wasUnlockCalled( )
+ {
+ return this.unlockCalled;
+ }
+
+
+ public boolean wasEndCycleCalled( )
+ {
+ return this.endCycleCalled;
+ }
+
+
+ public boolean wasExecuteCalled( )
+ {
+ return this.executeCalled;
+ }
+
+
+ public void setFailExecute( boolean failExecute )
+ {
+ this.failExecute = failExecute;
+ }
+
+
+ @Override
+ public boolean tryLock( )
+ {
+ this.tryLockCalled = true;
+ return true;
+ }
+
+
+ @Override
+ public void unlock( )
+ {
+ this.unlockCalled = true;
+ }
+
+
+ @Override
+ public boolean endPreviousCycle( )
+ {
+ this.endCycleCalled = true;
+ return false;
+ }
+
+
+ @Override
+ public void executeUpdateCycle( )
+ {
+ this.executeCalled = true;
+ if ( this.failExecute ) {
+ throw new RuntimeException( "fail" );
+ }
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java
new file mode 100644
index 0000000..60ac8fc
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockRegistry.java
@@ -0,0 +1,41 @@
+package com.deepclone.lw.beans.updates;
+
+
+import java.util.HashMap;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+
+
+
+/**
+ * A mock processor registry
+ *
+ * @author E. Benoît
+ */
+class MockRegistry
+ implements ServerProcessorRegistry
+{
+
+ /** The map of "processors" to return */
+ private final HashMap< String , UpdateBatchProcessor > processors = new HashMap< String , UpdateBatchProcessor >( );
+
+
+ /**
+ * Add a processor to the registry
+ *
+ * @param processor
+ * the processor to add
+ */
+ public void put( UpdateBatchProcessor processor )
+ {
+ this.processors.put( processor.getUpdateType( ) , processor );
+ }
+
+
+ @Override
+ public UpdateBatchProcessor getProcessorFor( String type )
+ {
+ return this.processors.get( type );
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java
new file mode 100644
index 0000000..5cf16d6
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/MockUpdatesDAO.java
@@ -0,0 +1,32 @@
+package com.deepclone.lw.beans.updates;
+
+
+import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
+import com.deepclone.lw.sqld.sys.GameUpdateResult;
+
+
+
+/**
+ * Mock updates DAO used in tests
+ *
+ * @author E. Benoît
+ */
+class MockUpdatesDAO
+ implements UpdatesDAO
+{
+ /** The index to read at the next call to {@link #processUpdates(long)} */
+ public int index = 0;
+
+ /** The values to return as the update type to process */
+ public String[] values = new String[] { };
+
+
+ @Override
+ public GameUpdateResult processUpdates( long tickId )
+ {
+ if ( this.index >= this.values.length ) {
+ return new GameUpdateResult( );
+ }
+ return new GameUpdateResult( this.values[ this.index++ ] );
+ }
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java
new file mode 100644
index 0000000..d6ff2da
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateProcessorBean.java
@@ -0,0 +1,240 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+
+import java.sql.Timestamp;
+import java.util.Date;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
+import com.deepclone.lw.interfaces.sys.MaintenanceData;
+import com.deepclone.lw.testing.MockLogger;
+import com.deepclone.lw.testing.MockSystemStatus;
+import com.deepclone.lw.testing.MockTransactionManager;
+
+
+
+/**
+ * Tests for {@link GameUpdateProcessorBean}
+ *
+ * @author E. Benoît
+ */
+public class TestGameUpdateProcessorBean
+{
+
+ /** The mock logger */
+ private MockLogger logger;
+
+ /** Mock updates access interface */
+ private MockUpdatesDAO updatesDAO;
+
+ /** Mock processor registry */
+ private MockRegistry registry;
+
+ /** Mock system status component */
+ private MockSystemStatus system;
+
+ /** The instance under test */
+ private GameUpdateProcessorBean gup;
+
+ /**
+ * A fake batch processor which actually enables maintenance mode.
+ *
+ * @author E. Benoît
+ */
+ private static class MaintenanceEnabler
+ implements UpdateBatchProcessor
+ {
+
+ /** Mock system status component */
+ private MockSystemStatus system;
+
+
+ /**
+ * Set the mock system status component
+ *
+ * @param system
+ * the mock system status component
+ */
+ MaintenanceEnabler( MockSystemStatus system )
+ {
+ this.system = system;
+ }
+
+
+ @Override
+ public String getUpdateType( )
+ {
+ return "maintenance";
+ }
+
+
+ @Override
+ public void processBatch( long tickId )
+ {
+ this.system.setMaintenance( new MaintenanceData( new Timestamp( new Date( ).getTime( ) ) , new Timestamp(
+ new Date( ).getTime( ) ) , "no reason" ) );
+ }
+
+ }
+
+
+ @Before
+ public void setUp( )
+ {
+ this.logger = new MockLogger( );
+ this.updatesDAO = new MockUpdatesDAO( );
+ this.updatesDAO.values = new String[] {
+ null
+ };
+ this.system = new MockSystemStatus( );
+ this.registry = new MockRegistry( );
+ this.registry.put( new MaintenanceEnabler( this.system ) );
+
+ this.gup = new GameUpdateProcessorBean( );
+ this.gup.setLogger( this.logger );
+ this.gup.setUpdatesDAO( this.updatesDAO );
+ this.gup.setRegistry( this.registry );
+ this.gup.setTransactionManager( new MockTransactionManager( ) );
+ this.gup.setSystemStatus( this.system );
+ }
+
+
+ /**
+ * Try locking the processor
+ */
+ @Test
+ public void testLocking( )
+ {
+ assertTrue( this.gup.tryLock( ) );
+ assertFalse( this.gup.tryLock( ) );
+ }
+
+
+ /** Try unlocking the processor */
+ @Test
+ public void testUnlocking( )
+ {
+ this.gup.tryLock( );
+ this.gup.unlock( );
+ assertTrue( this.gup.tryLock( ) );
+ }
+
+
+ /** Try unlocking the processor when it's not locked */
+ @Test( expected = IllegalStateException.class )
+ public void testUnlockingWithNoLock( )
+ {
+ this.gup.unlock( );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#endPreviousCycle()} returns false
when there is
+ * no "stuck" tick
+ */
+ @Test
+ public void testEndCycleNoStuckTick( )
+ {
+ assertFalse( this.gup.endPreviousCycle( ) );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#endPreviousCycle()} returns true
when maintenance
+ * mode is enabled, but the update is not processed
+ */
+ @Test
+ public void testEndCycleMaintenance( )
+ {
+ this.system.setCSTBehaviour( -2 );
+ assertTrue( this.gup.endPreviousCycle( ) );
+ assertEquals( 0 , this.updatesDAO.index );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#endPreviousCycle()} returns true
when there is a
+ * stuck tick, and the update is processed
+ */
+ @Test
+ public void testEndCycle( )
+ {
+ this.system.setCSTBehaviour( 0 );
+ assertTrue( this.gup.endPreviousCycle( ) );
+ assertEquals( 1 , this.updatesDAO.index );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#executeUpdateCycle()} does not start a tick if there was a
+ * stuck tick, but the previous tick is processed.
+ */
+ @Test
+ public void testProcessWithStuckTick( )
+ {
+ this.system.setCSTBehaviour( 0 );
+ this.gup.executeUpdateCycle( );
+ assertFalse( this.system.wasStartTickCalled( ) );
+ assertEquals( 1 , this.updatesDAO.index );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#executeUpdateCycle()} does not start a tick if maintenance
+ * mode was enabled from the start
+ */
+ @Test
+ public void testProcessWithStuckTickAndMaintenance( )
+ {
+ this.system.setCSTBehaviour( -2 );
+ this.gup.executeUpdateCycle( );
+ assertFalse( this.system.wasStartTickCalled( ) );
+ assertEquals( 0 , this.updatesDAO.index );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#executeUpdateCycle()} does not start a tick if maintenance
+ * mode is enabled between the check for stuck ticks and the new tick's start.
+ */
+ @Test
+ public void testProcessWithMaintenance( )
+ {
+ this.system.setSTBehaviour( -2 );
+ this.gup.executeUpdateCycle( );
+ assertTrue( this.system.wasStartTickCalled( ) );
+ assertEquals( 0 , this.updatesDAO.index );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#executeUpdateCycle()} throws a runtime exception if there
+ * seems to be a tick running when the new tick is supposed to start.
+ */
+ @Test( expected = RuntimeException.class )
+ public void testProcessWithBadState( )
+ {
+ this.system.setSTBehaviour( -1 );
+ this.gup.executeUpdateCycle( );
+ }
+
+
+ /**
+ * {@link GameUpdateProcessorBean#executeUpdateCycle()} stops processing mid-way if maintenance
+ * mode becomes enabled
+ */
+ @Test
+ public void testProcessWithMaintenanceEnabledMidWay( )
+ {
+ this.updatesDAO.values = new String[] {
+ null , "maintenance" , null
+ };
+ this.gup.executeUpdateCycle( );
+ assertTrue( this.system.wasStartTickCalled( ) );
+ assertEquals( 2 , this.updatesDAO.index );
+ }
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java
new file mode 100644
index 0000000..1f97148
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTaskBean.java
@@ -0,0 +1,93 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
+import com.deepclone.lw.testing.MockTicker;
+
+
+
+/**
+ * Tests for {@link GameUpdateTaskBean}
+ *
+ * @author E. Benoît
+ */
+public class TestGameUpdateTaskBean
+{
+ /** The mock ticker */
+ private MockTicker ticker;
+
+ /** The mock game update processor */
+ private MockGameUpdateProcessor processor;
+
+ /** The game update task */
+ private GameUpdateTaskBean gut;
+
+
+ /**
+ * Initialise the mock components and the game update task instance
+ */
+ @Before
+ public void setUp( )
+ {
+ this.ticker = new MockTicker( Frequency.MINUTE , "Game update" , GameUpdateTaskBean.class );
+ this.processor = new MockGameUpdateProcessor( );
+
+ this.gut = new GameUpdateTaskBean( );
+ this.gut.setTicker( this.ticker );
+ this.gut.setGameUpdateProcessor( this.processor );
+ }
+
+
+ /**
+ * Check the component's initialisation
+ */
+ @Test
+ public void testInitialisation( )
+ {
+ this.gut.afterPropertiesSet( );
+ assertTrue( this.processor.wasTryLockCalled( ) );
+ assertTrue( this.processor.wasUnlockCalled( ) );
+ assertTrue( this.processor.wasEndCycleCalled( ) );
+ assertFalse( this.processor.wasExecuteCalled( ) );
+ }
+
+
+ /**
+ * Check the component's run() method
+ */
+ @Test
+ public void testNormalRun( )
+ {
+ this.gut.run( );
+ assertTrue( this.processor.wasTryLockCalled( ) );
+ assertTrue( this.processor.wasUnlockCalled( ) );
+ assertFalse( this.processor.wasEndCycleCalled( ) );
+ assertTrue( this.processor.wasExecuteCalled( ) );
+ }
+
+
+ /**
+ * Make sure the component is unlocked even if the execution fails
+ */
+ @Test
+ public void testFailedRun( )
+ {
+ this.processor.setFailExecute( true );
+ try {
+ this.gut.run( );
+ fail( "Mock processor failed to fail" );
+ } catch ( RuntimeException e ) {
+ // EMPTY
+ }
+ assertTrue( this.processor.wasTryLockCalled( ) );
+ assertTrue( this.processor.wasUnlockCalled( ) );
+ assertFalse( this.processor.wasEndCycleCalled( ) );
+ assertTrue( this.processor.wasExecuteCalled( ) );
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java
new file mode 100644
index 0000000..2484435
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestGameUpdateTransaction.java
@@ -0,0 +1,86 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+
+
+/**
+ * Tests for {@link GameUpdateTransaction}
+ *
+ * @author E. Benoît
+ */
+public class TestGameUpdateTransaction
+{
+
+ /** The mock updates DAO */
+ private MockUpdatesDAO updatesDAO;
+
+ /** The dummy batch processor used in the tests */
+ private DummyBatchProcessor processor;
+
+ /** The mock processor registry */
+ private MockRegistry registry;
+
+ /** The game update transaction used as a "guinea pig" */
+ private GameUpdateTransaction transaction;
+
+
+ /** Set up the mock objects and the guinea pig transaction instance */
+ @Before
+ public void setUp( )
+ {
+ this.updatesDAO = new MockUpdatesDAO( );
+ this.registry = new MockRegistry( );
+ this.registry.put( this.processor = new DummyBatchProcessor( "test" ) );
+ this.transaction = new GameUpdateTransaction( this.updatesDAO , this.registry , 1 );
+ }
+
+
+ /** Test return value when the stored procedure says that no more updates are to be processed */
+ @Test
+ public void testWhenFinished( )
+ {
+ assertTrue( this.transaction.doInTransaction( null ) );
+ }
+
+
+ /** Test return value when the stored procedure says that more updates are to be processed */
+ @Test
+ public void testWhenHasMore( )
+ {
+ this.updatesDAO.values = new String[] {
+ null
+ };
+ assertFalse( this.transaction.doInTransaction( null ) );
+ }
+
+
+ /** Test what happens when the stored procedure indicates the need for local processing */
+ @Test
+ public void testLocalProcessing( )
+ {
+ this.updatesDAO.values = new String[] {
+ "test"
+ };
+ assertFalse( this.transaction.doInTransaction( null ) );
+ assertEquals( 1 , this.processor.getTicks( ).size( ) );
+ assertTrue( this.processor.getTicks( ).contains( 1L ) );
+ }
+
+
+ /**
+ * Test what happens when the stored procedure indicates the need for local processing but the
+ * specified processor does not exist
+ */
+ @Test( expected = UnsupportedUpdateException.class )
+ public void testMissingProcessor( )
+ {
+ this.updatesDAO.values = new String[] {
+ "missing processor"
+ };
+ this.transaction.doInTransaction( null );
+ }
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java
new file mode 100644
index 0000000..65fca67
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/beans/updates/TestServerProcessorRegistryBean.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.beans.updates;
+
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.BeanInitializationException;
+
+
+
+/**
+ * Tests for {@link ServerProcessorRegistryBean}
+ *
+ * @author E. Benoît
+ */
+public class TestServerProcessorRegistryBean
+{
+ /** The "guinea pig" instance */
+ private ServerProcessorRegistryBean registry;
+
+
+ @Before
+ public void setUp( )
+ {
+ this.registry = new ServerProcessorRegistryBean( );
+ this.registry.postProcessAfterInitialization( new DummyBatchProcessor( "test" ) , "test" );
+ }
+
+
+ /** The registry returns null
when the type has no processor */
+ @Test
+ public void testMissingReturnsNull( )
+ {
+ assertNull( this.registry.getProcessorFor( "does not exist" ) );
+ }
+
+
+ /** The registry returns the processor when there is one */
+ @Test
+ public void testExistingReturnsProcessor( )
+ {
+ assertNotNull( this.registry.getProcessorFor( "test" ) );
+ }
+
+
+ /** The registry ignores objects which are not batch processors */
+ @Test
+ public void testIgnoresOtherObjects( )
+ {
+ this.registry.postProcessAfterInitialization( "test" , "test" );
+ }
+
+
+ /** The registry crashes when two batch processors are defined for the same type */
+ @Test( expected = BeanInitializationException.class )
+ public void testFailsIfDuplicate( )
+ {
+ this.registry.postProcessAfterInitialization( new DummyBatchProcessor( "test" ) , "test" );
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java
new file mode 100644
index 0000000..a047453
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockLogger.java
@@ -0,0 +1,34 @@
+package com.deepclone.lw.testing;
+
+
+import java.util.HashMap;
+
+import com.deepclone.lw.interfaces.eventlog.Logger;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+
+
+
+/**
+ * A mock Logger component
+ *
+ * @author E. Benoît
+ *
+ */
+public class MockLogger
+ implements Logger
+{
+ /** Map of existing system loggers */
+ private final HashMap< String , MockSystemLogger > loggers = new HashMap< String , MockSystemLogger >( );
+
+
+ @Override
+ public SystemLogger getSystemLogger( String component )
+ {
+ MockSystemLogger logger = this.loggers.get( component );
+ if ( logger == null ) {
+ this.loggers.put( component , logger = new MockSystemLogger( ) );
+ }
+ return logger;
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java
new file mode 100644
index 0000000..5d15c20
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemLogger.java
@@ -0,0 +1,62 @@
+package com.deepclone.lw.testing;
+
+
+import com.deepclone.lw.cmd.admin.logs.LogLevel;
+import com.deepclone.lw.interfaces.eventlog.SystemLogger;
+
+
+
+/**
+ * A mock logger for components
+ *
+ * @author E. Benoît
+ */
+public class MockSystemLogger
+ implements SystemLogger
+{
+ /** How many messages were logged */
+ private int counter = 0;
+
+
+ /** @return the amount of messages that were logged */
+ public int getCounter( )
+ {
+ return counter;
+ }
+
+
+ /**
+ * Set the message counter
+ *
+ * @param counter
+ * the counter's new value
+ */
+ public void setCounter( int counter )
+ {
+ this.counter = counter;
+ }
+
+
+ @Override
+ public SystemLogger log( LogLevel level , String message )
+ {
+ this.counter++;
+ return this;
+ }
+
+
+ @Override
+ public SystemLogger log( LogLevel level , String message , Throwable exception )
+ {
+ this.counter++;
+ return this;
+ }
+
+
+ @Override
+ public SystemLogger flush( )
+ {
+ return this;
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java
new file mode 100644
index 0000000..52bb8f8
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockSystemStatus.java
@@ -0,0 +1,148 @@
+package com.deepclone.lw.testing;
+
+
+import com.deepclone.lw.interfaces.sys.MaintenanceData;
+import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
+import com.deepclone.lw.interfaces.sys.SystemStatus;
+import com.deepclone.lw.interfaces.sys.TickStatusException;
+
+
+
+/**
+ * A mock system status component
+ *
+ * @author E. Benoît
+ */
+public class MockSystemStatus
+ implements SystemStatus
+{
+ /** Maintenance data to return when {@link #checkMaintenance()} is called */
+ private MaintenanceData maintenance = null;
+
+ /** Value that determines the behaviour of {@link #startTick()} */
+ private long stValue = 0;
+
+ /** Value that determines the behaviour of {@link #checkStuckTick()} */
+ private long cstValue = -1;
+
+ /** Was {@link #startTick()} called? */
+ private boolean startTickCalled = false;
+
+
+ /**
+ * Set the maintenance data to return when {@link #checkMaintenance()} is called
+ *
+ * @param maintenance
+ * the data to return from {@link #checkMaintenance()}
+ */
+ public void setMaintenance( MaintenanceData maintenance )
+ {
+ this.maintenance = maintenance;
+ }
+
+
+ /**
+ * Set the value that determines the behaviour of {@link #checkStuckTick()}
+ *
+ *
+ * - -2 will cause a {@link MaintenanceStatusException},
+ *
- -1 will cause it to return
null
,
+ * - any other value will be returned.
+ *
+ *
+ * @param cstValue
+ * the value
+ */
+ public void setCSTBehaviour( long cstValue )
+ {
+ this.cstValue = cstValue;
+ }
+
+
+ /**
+ * Set the value that determines the behaviour of {@link #startTick()}
+ *
+ *
+ * - -2 will cause a {@link MaintenanceStatusException},
+ *
- -1 will cause a {@link TickStatusException},
+ *
- any other value will be returned.
+ *
+ *
+ * @param cstValue
+ * the value
+ */
+ public void setSTBehaviour( long stValue )
+ {
+ this.stValue = stValue;
+ }
+
+
+ /**
+ * @return true
if {@link #startTick()} was called.
+ */
+ public boolean wasStartTickCalled( )
+ {
+ return startTickCalled;
+ }
+
+
+ @Override
+ public MaintenanceData checkMaintenance( )
+ {
+ return this.maintenance;
+ }
+
+
+ @Override
+ public void startMaintenance( int adminId , String reason , int duration )
+ throws MaintenanceStatusException
+ {
+ // EMPTY - ignored
+ }
+
+
+ @Override
+ public void updateMaintenance( int adminId , int durationFromNow )
+ throws MaintenanceStatusException
+ {
+ // EMPTY - ignored
+ }
+
+
+ @Override
+ public void endMaintenance( int adminId )
+ throws MaintenanceStatusException
+ {
+ // EMPTY - ignored
+ }
+
+
+ @Override
+ public long startTick( )
+ throws TickStatusException , MaintenanceStatusException
+ {
+ this.startTickCalled = true;
+ if ( this.stValue == -1 ) {
+ throw new TickStatusException( );
+ }
+ if ( this.stValue == -2 ) {
+ throw new MaintenanceStatusException( );
+ }
+ return this.stValue;
+ }
+
+
+ @Override
+ public Long checkStuckTick( )
+ throws MaintenanceStatusException
+ {
+ if ( this.cstValue == -1 ) {
+ return null;
+ }
+ if ( this.cstValue == -2 ) {
+ throw new MaintenanceStatusException( );
+ }
+ return this.cstValue;
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java
new file mode 100644
index 0000000..6dc8fe9
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTicker.java
@@ -0,0 +1,82 @@
+package com.deepclone.lw.testing;
+
+
+import com.deepclone.lw.interfaces.sys.Ticker;
+
+import static org.junit.Assert.*;
+
+
+
+/**
+ * A mock ticker component
+ *
+ *
+ * This mock component can be used to make sure that another component registers some task as
+ * expected.
+ *
+ * @author E. Benoît
+ *
+ */
+public class MockTicker
+ implements Ticker
+{
+ /** The task's expected frequency */
+ private final Frequency expectedFrequency;
+
+ /** The task's expected name */
+ private final String expectedName;
+
+ /** The task's expected type */
+ private final Class< ? > expectedType;
+
+
+ /**
+ * Initialise the mock ticker's expectations
+ *
+ * @param expectedFrequency
+ * the task's expected frequency
+ * @param expectedName
+ * the task's expected name
+ * @param expectedType
+ * the task's expected type
+ */
+ public MockTicker( Frequency expectedFrequency , String expectedName , Class< ? > expectedType )
+ {
+ this.expectedFrequency = expectedFrequency;
+ this.expectedName = expectedName;
+ this.expectedType = expectedType;
+ }
+
+
+ @Override
+ public void registerTask( Frequency frequency , String name , Runnable task )
+ {
+ assertEquals( this.expectedFrequency , frequency );
+ assertEquals( this.expectedName , name );
+ assertTrue( this.expectedType.isInstance( task ) );
+ }
+
+
+ @Override
+ public void pause( )
+ throws IllegalStateException
+ {
+ // EMPTY - ignored
+ }
+
+
+ @Override
+ public void unpause( )
+ throws IllegalStateException
+ {
+ // EMPTY - ignored
+ }
+
+
+ @Override
+ public boolean isActive( )
+ {
+ return true;
+ }
+
+}
diff --git a/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java
new file mode 100644
index 0000000..3b68085
--- /dev/null
+++ b/legacyworlds-server-tests/src/test/java/com/deepclone/lw/testing/MockTransactionManager.java
@@ -0,0 +1,43 @@
+package com.deepclone.lw.testing;
+
+
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+
+
+
+/**
+ * A mock transaction manager
+ *
+ * @author E. Benoît
+ */
+public class MockTransactionManager
+ implements PlatformTransactionManager
+{
+
+ @Override
+ public TransactionStatus getTransaction( TransactionDefinition definition )
+ throws TransactionException
+ {
+ return null;
+ }
+
+
+ @Override
+ public void commit( TransactionStatus status )
+ throws TransactionException
+ {
+ // EMPTY
+ }
+
+
+ @Override
+ public void rollback( TransactionStatus status )
+ throws TransactionException
+ {
+ // EMPTY
+ }
+
+}
diff --git a/legacyworlds/pom.xml b/legacyworlds/pom.xml
index 86d3192..bd3479b 100644
--- a/legacyworlds/pom.xml
+++ b/legacyworlds/pom.xml
@@ -168,6 +168,11 @@
com.deepclone.lw
${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}
+
+ legacyworlds-server-beans-updates
+ com.deepclone.lw
+ ${legacyworlds.version.main}.${legacyworlds.version.release}-${legacyworlds.version.build}
+
legacyworlds-server-beans-user
com.deepclone.lw