├── GameLoopSystemInvocationStrategy.java ├── LogicRenderMarker.java └── README.md /GameLoopSystemInvocationStrategy.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2015 See AUTHORS file. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | 17 | 18 | package de.tomgrill.artemis; 19 | 20 | import com.artemis.BaseSystem; 21 | import com.artemis.SystemInvocationStrategy; 22 | import com.artemis.utils.BitVector; 23 | import com.badlogic.gdx.utils.Array; 24 | 25 | import java.util.concurrent.TimeUnit; 26 | 27 | 28 | /** 29 | * Implements a game loop based on this excellent blog post: 30 | * http://gafferongames.com/game-physics/fix-your-timestep/ 31 | * 32 | * To avoid floating point rounding errors we only use fixed point numbers for calculations. 33 | */ 34 | public class GameLoopSystemInvocationStrategy extends SystemInvocationStrategy { 35 | 36 | private final Array logicMarkedSystems; 37 | private final Array otherSystems; 38 | 39 | private long nanosPerLogicTick; // ~ dt 40 | private long currentTime = System.nanoTime(); 41 | 42 | private long accumulator; 43 | 44 | private boolean systemsSorted = false; 45 | 46 | private final BitVector disabledlogicMarkedSystems = new BitVector(); 47 | private final BitVector disabledOtherSystems = new BitVector(); 48 | 49 | public GameLoopSystemInvocationStrategy() { 50 | this(40); 51 | } 52 | 53 | @Override 54 | protected void initialize() { 55 | /** Sort Sytems here in case {@link #setEnabled(BaseSystem, boolean)} is called prior to first {@link #process()} */ 56 | if (!systemsSorted) { 57 | sortSystems(); 58 | } 59 | } 60 | 61 | 62 | public GameLoopSystemInvocationStrategy(int millisPerLogicTick) { 63 | this.nanosPerLogicTick = TimeUnit.MILLISECONDS.toNanos(millisPerLogicTick); 64 | logicMarkedSystems = new Array(); 65 | otherSystems = new Array(); 66 | } 67 | 68 | private void sortSystems() { 69 | if (!systemsSorted) { 70 | Object[] systemsData = systems.getData(); 71 | for (int i = 0, s = systems.size(); s > i; i++) { 72 | BaseSystem system = (BaseSystem) systemsData[i]; 73 | if (system instanceof LogicRenderMarker) { 74 | logicMarkedSystems.add(system); 75 | } else { 76 | otherSystems.add(system); 77 | } 78 | } 79 | systemsSorted = true; 80 | } 81 | } 82 | 83 | @Override 84 | protected void process() { 85 | if (!systemsSorted) { 86 | sortSystems(); 87 | } 88 | 89 | long newTime = System.nanoTime(); 90 | long frameTime = newTime - currentTime; 91 | 92 | if (frameTime > 250000000) { 93 | frameTime = 250000000; // Note: Avoid spiral of death 94 | } 95 | 96 | currentTime = newTime; 97 | accumulator += frameTime; 98 | 99 | // required since artemis-odb-2.0.0-RC4, updateEntityStates() must be called 100 | // before processing the first system - in case any entities are 101 | // added outside the main process loop 102 | updateEntityStates(); 103 | 104 | /** 105 | * Uncomment this line if you use the world's delta within your systems. 106 | * I recommend to use a fixed value for your logic delta like millisPerLogicTick or nanosPerLogicTick 107 | */ 108 | // world.setDelta(nanosPerLogicTick * 0.000000001f); 109 | 110 | while (accumulator >= nanosPerLogicTick) { 111 | /** Process all entity systems inheriting from {@link LogicRenderMarker} */ 112 | for (int i = 0; i < logicMarkedSystems.size; i++) { 113 | /** 114 | * Make sure your systems keep the current state before calculating the new state 115 | * else you cannot interpolate later on when rendering 116 | */ 117 | if (disabledlogicMarkedSystems.get(i)) { 118 | continue; 119 | } 120 | logicMarkedSystems.get(i).process(); 121 | updateEntityStates(); 122 | } 123 | 124 | accumulator -= nanosPerLogicTick; 125 | } 126 | 127 | /** 128 | * Uncomment this line if you use the world's delta within your systems. 129 | */ 130 | // world.setDelta(frameTime * 0.000000001f); 131 | 132 | /** 133 | * When you divide accumulator by nanosPerLogicTick you get your alpha. 134 | * You can store the alpha value in a GameStateComponent f.e. 135 | */ 136 | // float alpha = (float) accumulator / nanosPerLogicTick; 137 | 138 | 139 | /** Process all NON {@link LogicRenderMarker} inheriting entity systems */ 140 | for (int i = 0; i < otherSystems.size; i++) { 141 | /** 142 | * Use the kept state from the logic above and interpolate with the current state within your render systems. 143 | */ 144 | if (disabledOtherSystems.get(i)) { 145 | continue; 146 | } 147 | otherSystems.get(i).process(); 148 | updateEntityStates(); 149 | } 150 | } 151 | 152 | @Override 153 | public boolean isEnabled(BaseSystem target) { 154 | Array systems = (target instanceof LogicRenderMarker) ? logicMarkedSystems : otherSystems; 155 | BitVector disabledSystems = (target instanceof LogicRenderMarker) ? disabledlogicMarkedSystems : disabledOtherSystems; 156 | Class targetClass = target.getClass(); 157 | for (int i = 0; i < systems.size; i++) { 158 | if (targetClass == systems.get(i).getClass()) 159 | return !disabledSystems.get(i); 160 | } 161 | throw new RuntimeException("System not found in this world"); 162 | } 163 | 164 | @Override 165 | public void setEnabled(BaseSystem target, boolean value) { 166 | Array systems = (target instanceof LogicRenderMarker) ? logicMarkedSystems : otherSystems; 167 | BitVector disabledSystems = (target instanceof LogicRenderMarker) ? disabledlogicMarkedSystems : disabledOtherSystems; 168 | Class targetClass = target.getClass(); 169 | for (int i = 0; i < systems.size; i++) { 170 | if (targetClass == systems.get(i).getClass()) { 171 | disabledSystems.set(i, !value); 172 | break; 173 | } 174 | } 175 | } 176 | } 177 | 178 | -------------------------------------------------------------------------------- /LogicRenderMarker.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2015 See AUTHORS file. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | 17 | 18 | package de.tomgrill.artemis; 19 | 20 | /** 21 | * Simple marker interface to declare a EntitySystem to be processed only in the logic part of the game loop 22 | */ 23 | public interface LogicRenderMarker { 24 | } 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logic-render-game-loop 2 | 3 | artemis-odb SystemInvocationStrategy to seperate game logic and rendering within the game loop. 4 | 5 | ## Updates & News 6 | Follow me to receive release updates about this and my other projects (Promise: No BS posts) 7 | 8 | https://twitter.com/TomGrillGames and https://www.facebook.com/tomgrillgames 9 | 10 | I will also stream sometimes when developing at https://www.twitch.tv/tomgrill and write a blog article from time to time at http://tomgrill.de 11 | 12 | ### Requires: 13 | https://github.com/junkdog/artemis-odb 14 | 15 | https://github.com/libgdx/libgdx 16 | 17 | 18 | ###Usage: 19 | Register `GameLoopSystemInvocationStrategy` with your `WorldConfiguration` 20 | 21 | 22 | ```java 23 | WorldConfiguration config = new WorldConfigurationBuilder() 24 | .dependsOn(MyPlugin.class) 25 | .with( 26 | new MySystemA(), 27 | new MySystemB(), 28 | new MySystemC(), 29 | ) 30 | .register(new GameLoopSystemInvocationStrategy(40)) // millis per logic tick. default: 40 ~ 25ticks/second 31 | .build(); 32 | ``` 33 | 34 | Mark all EntitySystem for processing within the logic part of the game loop like this: 35 | ```java 36 | public class MySuperLogicSystem extends EntityProcessingSystem implements LogicRenderEntitySystem{ 37 | 38 | public MySuperLogicSystem(Aspect.Builder aspect) { 39 | super(aspect); 40 | } 41 | 42 | @Override 43 | protected void process(Entity e) { 44 | System.out.println("I will only processed when it's logic time."); 45 | } 46 | } 47 | ``` 48 | 49 | ### License 50 | The logic-render-game-loop 51 | project is licensed under the Apache 2 License, meaning you can use it free of charge, without strings attached in commercial and non-commercial projects. We love to get (non-mandatory) credit in case you release a game or app using gdx-facebook! 52 | --------------------------------------------------------------------------------