Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
ziqian zhang
Grasscutter
Commits
0cb49060
Commit
0cb49060
authored
Jun 22, 2022
by
KingRainbow44
Browse files
Re-implement scheduler system (check #1321)
parent
63b6b805
Changes
5
Show whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/server/event/game/ServerTickEvent.java
View file @
0cb49060
package
emu.grasscutter.server.event.game
;
import
emu.grasscutter.server.event.types.ServerEvent
;
import
java.time.Instant
;
import
java.time.Instant
;
public
final
class
ServerTickEvent
extends
ServerEvent
{
public
ServerTickEvent
()
{
private
final
Instant
start
,
end
;
public
ServerTickEvent
(
Instant
start
,
Instant
end
)
{
super
(
Type
.
GAME
);
this
.
start
=
start
;
this
.
end
=
end
;
}
public
Instant
getTickStart
()
{
return
this
.
start
;
}
public
Instant
getTickEnd
()
{
return
this
.
end
;
}
public
Instant
getTickStart
()
{
return
this
.
start
;
}
public
Instant
getTickEnd
()
{
return
this
.
end
;
}
}
\ No newline at end of file
src/main/java/emu/grasscutter/server/game/GameServer.java
View file @
0cb49060
...
...
@@ -26,11 +26,14 @@ import emu.grasscutter.server.event.types.ServerEvent;
import
emu.grasscutter.server.event.game.ServerTickEvent
;
import
emu.grasscutter.server.event.internal.ServerStartEvent
;
import
emu.grasscutter.server.event.internal.ServerStopEvent
;
import
emu.grasscutter.server.scheduler.ServerTaskScheduler
;
import
emu.grasscutter.task.TaskMap
;
import
kcp.highway.ChannelConfig
;
import
kcp.highway.KcpServer
;
import
lombok.Getter
;
import
java.net.InetSocketAddress
;
import
java.time.Instant
;
import
java.time.OffsetDateTime
;
import
java.util.*
;
import
java.util.concurrent.ConcurrentHashMap
;
...
...
@@ -42,6 +45,7 @@ public final class GameServer extends KcpServer {
private
final
InetSocketAddress
address
;
private
final
GameServerPacketHandler
packetHandler
;
private
final
ServerQuestHandler
questHandler
;
@Getter
private
final
ServerTaskScheduler
scheduler
;
private
final
Map
<
Integer
,
Player
>
players
;
private
final
Set
<
World
>
worlds
;
...
...
@@ -80,6 +84,7 @@ public final class GameServer extends KcpServer {
this
.
address
=
address
;
this
.
packetHandler
=
new
GameServerPacketHandler
(
PacketHandler
.
class
);
this
.
questHandler
=
new
ServerQuestHandler
();
this
.
scheduler
=
new
ServerTaskScheduler
();
this
.
players
=
new
ConcurrentHashMap
<>();
this
.
worlds
=
Collections
.
synchronizedSet
(
new
HashSet
<>());
...
...
@@ -238,7 +243,10 @@ public final class GameServer extends KcpServer {
return
DatabaseHelper
.
getAccountByName
(
username
);
}
public
synchronized
void
onTick
(){
public
synchronized
void
onTick
()
{
var
tickStart
=
Instant
.
now
();
// Tick worlds.
Iterator
<
World
>
it
=
this
.
getWorlds
().
iterator
();
while
(
it
.
hasNext
())
{
World
world
=
it
.
next
();
...
...
@@ -250,11 +258,17 @@ public final class GameServer extends KcpServer {
world
.
onTick
();
}
// Tick players.
for
(
Player
player
:
this
.
getPlayers
().
values
())
{
player
.
onTick
();
}
ServerTickEvent
event
=
new
ServerTickEvent
();
event
.
call
();
// Tick scheduler.
this
.
getScheduler
().
runTasks
();
// Call server tick event.
ServerTickEvent
event
=
new
ServerTickEvent
(
tickStart
,
Instant
.
now
());
event
.
call
();
}
public
void
registerWorld
(
World
world
)
{
...
...
src/main/java/emu/grasscutter/server/scheduler/AsyncServerTask.java
0 → 100644
View file @
0cb49060
package
emu.grasscutter.server.scheduler
;
import
lombok.Getter
;
import
javax.annotation.Nullable
;
/**
* A server task that should be run asynchronously.
*/
public
final
class
AsyncServerTask
implements
Runnable
{
/* The runnable to run. */
private
final
Runnable
task
;
/* This ID is assigned by the scheduler. */
@Getter
private
final
int
taskId
;
/* The result callback to run. */
@Nullable
private
final
Runnable
callback
;
/* Has the task already been started? */
private
boolean
started
=
false
;
/* Has the task finished execution? */
private
boolean
finished
=
false
;
/* The result produced in the async task. */
@Nullable
private
Object
result
=
null
;
/**
* For tasks without a callback.
* @param task The task to run.
*/
public
AsyncServerTask
(
Runnable
task
,
int
taskId
)
{
this
(
task
,
null
,
taskId
);
}
/**
* For tasks with a callback.
* @param task The task to run.
* @param callback The task to run after the task is complete.
*/
public
AsyncServerTask
(
Runnable
task
,
@Nullable
Runnable
callback
,
int
taskId
)
{
this
.
task
=
task
;
this
.
callback
=
callback
;
this
.
taskId
=
taskId
;
}
/**
* Returns the state of the task.
* @return True if the task has been started, false otherwise.
*/
public
boolean
hasStarted
()
{
return
this
.
started
;
}
/**
* Returns the state of the task.
* @return True if the task has finished execution, false otherwise.
*/
public
boolean
isFinished
()
{
return
this
.
finished
;
}
/**
* Runs the task.
*/
@Override
public
void
run
()
{
// Declare the task as started.
this
.
started
=
true
;
// Run the runnable.
this
.
task
.
run
();
// Declare the task as finished.
this
.
finished
=
true
;
}
/**
* Runs the callback.
*/
public
void
complete
()
{
// Run the callback.
if
(
this
.
callback
!=
null
)
this
.
callback
.
run
();
}
/**
* Sets the result of the async task.
* @param result The result of the async task.
*/
public
void
setResult
(
@Nullable
Object
result
)
{
this
.
result
=
result
;
}
/**
* Returns the set result of the async task.
* @return The result, or null if it has not been set.
*/
@Nullable
public
Object
getResult
()
{
return
this
.
result
;
}
}
src/main/java/emu/grasscutter/server/scheduler/ServerTask.java
0 → 100644
View file @
0cb49060
package
emu.grasscutter.server.scheduler
;
import
emu.grasscutter.Grasscutter
;
import
lombok.*
;
/**
* This class works the same as a runnable, except with more information.
*/
public
final
class
ServerTask
implements
Runnable
{
/* The runnable to run. */
private
final
Runnable
runnable
;
/* This ID is assigned by the scheduler. */
@Getter
private
final
int
taskId
;
/* The period at which the task should be run. */
/* The delay between the first execute. */
private
final
int
period
,
delay
;
public
ServerTask
(
Runnable
runnable
,
int
taskId
,
int
period
,
int
delay
)
{
this
.
runnable
=
runnable
;
this
.
taskId
=
taskId
;
this
.
period
=
period
;
this
.
delay
=
delay
;
}
/* The amount of times the task has been run. */
@Getter
private
int
ticks
=
0
;
/* Should the check consider delay? */
private
boolean
considerDelay
=
true
;
/**
* Cancels the task from running the next time.
*/
public
void
cancel
()
{
Grasscutter
.
getGameServer
().
getScheduler
().
cancelTask
(
this
.
taskId
);
}
/**
* Checks if the task should run at the current tick.
* @return True if the task should run, false otherwise.
*/
public
boolean
shouldRun
()
{
if
(
this
.
delay
!=
-
1
&&
this
.
considerDelay
)
{
this
.
considerDelay
=
false
;
return
this
.
ticks
==
this
.
delay
;
}
else
if
(
this
.
period
!=
-
1
)
return
this
.
ticks
%
this
.
period
==
0
;
else
return
true
;
}
/**
* Checks if the task should be canceled.
* @return True if the task should be canceled, false otherwise.
*/
public
boolean
shouldCancel
()
{
return
this
.
period
==
-
1
;
}
/**
* Runs the task.
*/
@Override
public
void
run
()
{
// Run the runnable.
this
.
runnable
.
run
();
// Increase tick count.
this
.
ticks
++;
}
}
\ No newline at end of file
src/main/java/emu/grasscutter/server/scheduler/ServerTaskScheduler.java
0 → 100644
View file @
0cb49060
package
emu.grasscutter.server.scheduler
;
import
java.util.concurrent.ConcurrentHashMap
;
/**
* A class to manage all time-based tasks scheduled on the server.
* This handles both synchronous and asynchronous tasks.
*
* Developers note: A server tick is ONE REAL-TIME SECOND.
*/
public
final
class
ServerTaskScheduler
{
/* A map to contain all running tasks. */
private
final
ConcurrentHashMap
<
Integer
,
ServerTask
>
tasks
=
new
ConcurrentHashMap
<>();
/* A map to contain all async tasks. */
private
final
ConcurrentHashMap
<
Integer
,
AsyncServerTask
>
asyncTasks
=
new
ConcurrentHashMap
<>();
/* The ID assigned to the next runnable. */
private
int
nextTaskId
=
0
;
/**
* Ran every server tick.
* Attempts to run all scheduled tasks.
* This method is synchronous and will block until all tasks are complete.
*/
public
void
runTasks
()
{
// Skip if there are no tasks.
if
(
this
.
tasks
.
size
()
==
0
)
return
;
// Run all tasks.
for
(
ServerTask
task
:
this
.
tasks
.
values
())
{
// Check if the task should run.
if
(
task
.
shouldRun
())
{
// Run the task.
task
.
run
();
}
// Check if the task should be canceled.
if
(
task
.
shouldCancel
())
{
// Cancel the task.
this
.
cancelTask
(
task
.
getTaskId
());
}
}
// Run all async tasks.
for
(
AsyncServerTask
task
:
this
.
asyncTasks
.
values
())
{
if
(!
task
.
hasStarted
())
{
// Create a thread for the task.
Thread
thread
=
new
Thread
(
task
);
// Start the thread.
thread
.
start
();
}
else
if
(
task
.
isFinished
())
{
// Cancel the task.
this
.
asyncTasks
.
remove
(
task
.
getTaskId
());
// Run the task's callback.
task
.
complete
();
}
}
}
/**
* Gets a task from the scheduler.
* @param taskId The ID of the task to get.
* @return The task, or null if it does not exist.
*/
public
ServerTask
getTask
(
int
taskId
)
{
return
this
.
tasks
.
get
(
taskId
);
}
/**
* Gets an async task from the scheduler.
* @param taskId The ID of the task to get.
* @return The task, or null if it does not exist.
*/
public
AsyncServerTask
getAsyncTask
(
int
taskId
)
{
return
this
.
asyncTasks
.
get
(
taskId
);
}
/**
* Removes a task from the scheduler.
* @param taskId The ID of the task to remove.
*/
public
void
cancelTask
(
int
taskId
)
{
this
.
tasks
.
remove
(
taskId
);
}
/**
* Schedules a task to be run on a separate thread.
* The task runs on the next server tick.
* @param runnable The runnable to run.
* @return The ID of the task.
*/
public
int
scheduleAsyncTask
(
Runnable
runnable
)
{
// Get the next task ID.
var
taskId
=
this
.
nextTaskId
++;
// Create a new task.
this
.
asyncTasks
.
put
(
taskId
,
new
AsyncServerTask
(
runnable
,
taskId
));
// Return the task ID.
return
taskId
;
}
/**
* Schedules a task to be run on the next server tick.
* @param runnable The runnable to run.
* @return The ID of the task.
*/
public
int
scheduleTask
(
Runnable
runnable
)
{
return
this
.
scheduleDelayedRepeatingTask
(
runnable
,
-
1
,
-
1
);
}
/**
* Schedules a task to be run after the amount of ticks has passed.
* @param runnable The runnable to run.
* @param delay The amount of ticks to wait before running.
* @return The ID of the task.
*/
public
int
scheduleDelayedTask
(
Runnable
runnable
,
int
delay
)
{
return
this
.
scheduleDelayedRepeatingTask
(
runnable
,
-
1
,
delay
);
}
/**
* Schedules a task to be run every amount of ticks.
* @param runnable The runnable to run.
* @param period The amount of ticks to wait before running again.
* @return The ID of the task.
*/
public
int
scheduleRepeatingTask
(
Runnable
runnable
,
int
period
)
{
return
this
.
scheduleDelayedRepeatingTask
(
runnable
,
period
,
0
);
}
/**
* Schedules a task to be run after the amount of ticks has passed.
* @param runnable The runnable to run.
* @param period The amount of ticks to wait before running again.
* @param delay The amount of ticks to wait before running the first time.
* @return The ID of the task.
*/
public
int
scheduleDelayedRepeatingTask
(
Runnable
runnable
,
int
period
,
int
delay
)
{
// Get the next task ID.
var
taskId
=
this
.
nextTaskId
++;
// Create a new task.
this
.
tasks
.
put
(
taskId
,
new
ServerTask
(
runnable
,
taskId
,
period
,
delay
));
// Return the task ID.
return
taskId
;
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment