diff --git a/src/dawn/asset/AssetManager.cpp b/src/dawn/asset/AssetManager.cpp
index 97d07c9b..922434c5 100644
--- a/src/dawn/asset/AssetManager.cpp
+++ b/src/dawn/asset/AssetManager.cpp
@@ -27,13 +27,45 @@ void AssetManager::update() {
   }
 }
 
+void AssetManager::removeExisting(const std::string filename) {
+  auto existing = std::find_if(
+    pendingAssetLoaders.begin(), pendingAssetLoaders.end(),
+    [&](auto &loader) {
+      return loader->name == filename;
+    }
+  );
+  if(existing != pendingAssetLoaders.end()) {
+    pendingAssetLoaders.erase(existing);
+  }
+
+  existing = std::find_if(
+    finishedAssetLoaders.begin(), finishedAssetLoaders.end(),
+    [&](auto &loader) {
+      return loader->name == filename;
+    }
+  );
+  if(existing != finishedAssetLoaders.end()) {
+    finishedAssetLoaders.erase(existing);
+  }
+}
+
 template<>
 std::shared_ptr<Texture> AssetManager::get<Texture>(
   const std::string filename
 ) {
-  // TODO: Check for existing loader
+  
+  auto existing = this->getExisting<TextureLoader>(filename);
+  if(existing) {
+    std::cout << "Found existing texture loader for " << filename << std::endl;
+
+    // Check pointer hasn't gone stale, if it has remove it and create new.
+    auto texture = existing->weakTexture.lock();
+    if(texture) return texture;
+
+    std::cout << "Texture loader for " << filename << " has gone stale." << std::endl;
+    this->removeExisting(filename);
+  }
 
-  // Create new loader
   std::shared_ptr<TextureLoader> loader = std::make_shared<TextureLoader>(
     filename
   );
diff --git a/src/dawn/asset/AssetManager.hpp b/src/dawn/asset/AssetManager.hpp
index 3a878b1b..a71ccd00 100644
--- a/src/dawn/asset/AssetManager.hpp
+++ b/src/dawn/asset/AssetManager.hpp
@@ -13,6 +13,42 @@ namespace Dawn {
       std::vector<std::shared_ptr<AssetLoader>> pendingAssetLoaders;
       std::vector<std::shared_ptr<AssetLoader>> finishedAssetLoaders;
 
+      /**
+       * Returns an existing asset loader if it exists.
+       * 
+       * @param filename The filename of the asset to get.
+       * @return The asset loader if it exists, otherwise nullptr.
+       */
+      template<class T>
+      std::shared_ptr<T> getExisting(const std::string filename) {
+        auto existing = std::find_if(
+          pendingAssetLoaders.begin(), pendingAssetLoaders.end(),
+          [&](auto &loader) {
+            return loader->name == filename;
+          }
+        );
+
+        if(existing == finishedAssetLoaders.end()) {
+          existing = std::find_if(
+            finishedAssetLoaders.begin(), finishedAssetLoaders.end(),
+            [&](auto &loader) {
+              return loader->name == filename;
+            }
+          );
+          
+          if(existing == finishedAssetLoaders.end()) return nullptr;
+        }
+
+        return std::static_pointer_cast<T>(*existing);
+      }
+
+      /**
+       * Removes an existing asset loader if it exists.
+       * 
+       * @param filename The filename of the asset to remove.
+       */
+      void removeExisting(const std::string filename);
+
     public:
       /**
        * Initializes this asset manager so it can begin accepting assets.
diff --git a/src/dawn/event/CustomEvent.hpp b/src/dawn/event/CustomEvent.hpp
new file mode 100644
index 00000000..a2622dbb
--- /dev/null
+++ b/src/dawn/event/CustomEvent.hpp
@@ -0,0 +1,141 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "dawnlibs.hpp"
+
+namespace Dawn {
+  enum class CustomEventResult {
+    NOTHING,
+    REMOVE,
+    INVOKE,
+    INVOKE_AND_REMOVE
+  };
+
+  template<
+    typename InternalData,
+    typename InternalListenerData,
+    typename ListenerArgument,
+    typename ...InvokeArgs
+  >
+  class CustomEvent {
+    private:
+      int32_t nextId = 0;
+      std::unordered_map<
+        int32_t,
+        std::pair<InternalListenerData, std::function<void(InvokeArgs...)>>
+      > listeners;
+
+    protected:
+      InternalData internalData;
+
+      /**
+       * Custom event filter. Decides whether or not the event should be emitted
+       * to the listener.
+       * 
+       * @param listenerData Data for this listener.
+       * @param args The arguments to pass to the listeners.
+       * @return The result of the filter.
+       */
+      virtual enum CustomEventResult shouldEmit(
+        const InternalListenerData &listenerData,
+        const InvokeArgs... args
+      ) = 0;
+
+      /**
+       * Transform the arguments for listener data when the listener is first
+       * subscribed.
+       * 
+       * @param argument The argument to transform.
+       * @return The transformed argument into an internal data format.
+       */
+      virtual InternalListenerData transformData(
+        const ListenerArgument &argument
+      ) = 0;
+
+      /**
+       * Transform the data for listener data after the event has been emitted.
+       * 
+       * @param internalData The internal data to transform.
+       * @return Updated/Transformed internal data.
+       */
+      virtual InternalListenerData transformDataAfterEmit(
+        const InternalListenerData &internalData
+      ) {
+        return internalData;
+      }
+
+    public:
+      /**
+       * Emits the event.
+       * @param args The arguments to pass to the listeners.
+       */
+      void emit(InvokeArgs... args) {
+        auto copy = listeners;
+        for(auto &pair : copy) {
+          // Check emit test.
+          auto result = this->shouldEmit(
+            pair.second.first,
+            args...
+          );
+          if(
+            result == CustomEventResult::INVOKE ||
+            result == CustomEventResult::INVOKE_AND_REMOVE
+          ) {
+            pair.second.second(args...);
+          }
+
+          if(
+            result == CustomEventResult::REMOVE || 
+            result == CustomEventResult::INVOKE_AND_REMOVE
+          ) {
+            listeners.erase(pair.first);
+            continue;
+          }
+
+          if(
+            result == CustomEventResult::INVOKE ||
+            result == CustomEventResult::INVOKE_AND_REMOVE
+          ) {
+            // Update the internal data.
+            listeners[pair.first].first = transformDataAfterEmit(
+              pair.second.first
+            );
+          }
+        }
+      }
+
+      /**
+       * Listens to the event.
+       * 
+       * @param data Listener data to use.
+       * @param listener The listener to add.
+       * @returns A function that can be called to remove the listener.
+       */
+      std::function<void()> listen(
+        const ListenerArgument &data,
+        const std::function<void(InvokeArgs...)> listener
+      ) {
+        int32_t id = nextId++;
+
+        auto pair = std::make_pair(
+          transformData(data),
+          listener
+        );
+
+        listeners[id] = pair;
+        return [this, id]() {
+          listeners.erase(id);
+        };
+      }
+
+      /**
+       * Destroys the custom event.
+       */
+      virtual ~CustomEvent() {
+        listeners.clear();
+      }
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/event/Event.hpp b/src/dawn/event/Event.hpp
index 638da515..5204a029 100644
--- a/src/dawn/event/Event.hpp
+++ b/src/dawn/event/Event.hpp
@@ -37,7 +37,9 @@ namespace Dawn {
        * @param listener The listener to add.
        * @returns A function that can be called to remove the listener.
        */
-      std::function<void()> listen(const std::function<void(A...)> listener) {
+      std::function<void()> listen(
+        const std::function<void(A...)> listener
+      ) {
         int32_t id = nextId++;
         listeners[id] = listener;
         return [this, id]() {
@@ -46,8 +48,7 @@ namespace Dawn {
       }
 
       /**
-       * Removes a listener from the event.
-       * @param id The id of the listener to remove.
+       * Destroys the event.
        */
       virtual ~Event() {
         listeners.clear();
diff --git a/src/dawn/scene/Scene.cpp b/src/dawn/scene/Scene.cpp
index 50f177e9..735baf95 100644
--- a/src/dawn/scene/Scene.cpp
+++ b/src/dawn/scene/Scene.cpp
@@ -14,7 +14,6 @@ Scene::Scene(
   game(game),
   sceneInitializer(sceneInitializer)
 {
-
 }
 
 void Scene::stage() {
@@ -23,18 +22,33 @@ void Scene::stage() {
 }
 
 void Scene::update() {
+  // Initialize new scene items
   if(!hasInitialized) {
     hasInitialized = true;
     for(auto &item : sceneItems) {
       item->init();
     }
   }
+  
+  // Remove stale scene items.
+  auto itRemove = sceneItemsToRemove.begin();
+  while(itRemove != sceneItemsToRemove.end()) {
+    auto item = *itRemove;
+    auto it = std::find(sceneItems.begin(), sceneItems.end(), item);
+    if(it != sceneItems.end()) sceneItems.erase(it);
+    itRemove++;
+  }
+  sceneItemsToRemove.clear();
 
+  // Tick scene items.
   float_t delta = getGame()->timeManager.delta;
   if(paused) {
     onPausedUpdate.emit(delta);
   } else {
     onUnpausedUpdate.emit(delta);
+    
+    onTimeout.tick(delta);
+    onInterval.tick(delta);
   }
 }
 
@@ -48,5 +62,9 @@ std::shared_ptr<SceneItem> Scene::createSceneItem() {
   return item;
 }
 
+void Scene::removeItem(const std::shared_ptr<SceneItem> item) {
+  sceneItemsToRemove.push_back(item);
+}
+
 Scene::~Scene() {
 }
\ No newline at end of file
diff --git a/src/dawn/scene/Scene.hpp b/src/dawn/scene/Scene.hpp
index 71d07f91..095a866f 100644
--- a/src/dawn/scene/Scene.hpp
+++ b/src/dawn/scene/Scene.hpp
@@ -6,19 +6,30 @@
 #pragma once
 #include "game/Game.hpp"
 #include "scene/SceneItem.hpp"
+#include "event/Event.hpp"
+#include "time/event/IntervalEvent.hpp"
+#include "time/event/TimeoutEvent.hpp"
 
 namespace Dawn {
+  struct IntervalData {
+    float_t frequency;
+    float_t nextInvoke;
+  };
+  
   class Scene final : public std::enable_shared_from_this<Scene> {
     private:
       std::weak_ptr<Game> game;
       const std::function<void(Scene&)> sceneInitializer;
       std::vector<std::shared_ptr<SceneItem>> sceneItems;
+      std::vector<std::shared_ptr<SceneItem>> sceneItemsToRemove;
       bool_t paused = false;
       bool_t hasInitialized = false;
 
     public:
       Event<float_t> onUnpausedUpdate;
       Event<float_t> onPausedUpdate;
+      TimeoutEvent onTimeout;
+      IntervalEvent onInterval;
 
       /**
        * Constructs a scene object.
@@ -57,6 +68,14 @@ namespace Dawn {
        */
       std::shared_ptr<SceneItem> createSceneItem();
 
+      /**
+       * Removes a scene item from the scene. This remove will happen at the 
+       * start of the next scene update.
+       * 
+       * @param item Scene item to remove.
+       */
+      void removeItem(const std::shared_ptr<SceneItem> item);
+
       /**
        * Returns a list of scene components that match the given type.
        * 
diff --git a/src/dawn/scene/SceneItem.cpp b/src/dawn/scene/SceneItem.cpp
index 0ca1d838..33ef3669 100644
--- a/src/dawn/scene/SceneItem.cpp
+++ b/src/dawn/scene/SceneItem.cpp
@@ -30,6 +30,12 @@ void SceneItem::init() {
   }
 }
 
+void SceneItem::remove() {
+  auto scene = getScene();
+  if(!scene) return;
+  scene->removeItem(shared_from_this());
+}
+
 SceneItem::~SceneItem() {
   std::for_each(
     components.begin(),
diff --git a/src/dawn/scene/SceneItem.hpp b/src/dawn/scene/SceneItem.hpp
index 1c2b464b..32106923 100644
--- a/src/dawn/scene/SceneItem.hpp
+++ b/src/dawn/scene/SceneItem.hpp
@@ -42,6 +42,12 @@ namespace Dawn {
        */
       std::shared_ptr<Scene> getScene();
 
+      /**
+       * Queues this scene item to be removed from the scene (on the next scene
+       * update).
+       */
+      void remove();
+
       /**
        * Returns the scene that this item belongs to.
        * 
diff --git a/src/dawn/time/CMakeLists.txt b/src/dawn/time/CMakeLists.txt
index 20bae16f..07371285 100644
--- a/src/dawn/time/CMakeLists.txt
+++ b/src/dawn/time/CMakeLists.txt
@@ -6,4 +6,7 @@
 target_sources(${DAWN_TARGET_NAME}
   PRIVATE
     ITimeManager.cpp
-)
\ No newline at end of file
+)
+
+# Subdirs
+add_subdirectory(event)
\ No newline at end of file
diff --git a/src/dawn/time/event/CMakeLists.txt b/src/dawn/time/event/CMakeLists.txt
new file mode 100644
index 00000000..514f06a7
--- /dev/null
+++ b/src/dawn/time/event/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (c) 2023 Dominic Masters
+# 
+# This software is released under the MIT License.
+# https://opensource.org/licenses/MIT
+
+target_sources(${DAWN_TARGET_NAME}
+  PRIVATE
+    IntervalEvent.cpp
+    TimeoutEvent.cpp
+)
\ No newline at end of file
diff --git a/src/dawn/time/event/IntervalEvent.cpp b/src/dawn/time/event/IntervalEvent.cpp
new file mode 100644
index 00000000..a7025788
--- /dev/null
+++ b/src/dawn/time/event/IntervalEvent.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "IntervalEvent.hpp"
+
+using namespace Dawn;
+
+IntervalEvent::IntervalEvent() : CustomEvent() {
+  internalData = 0.0f;
+}
+
+enum CustomEventResult IntervalEvent::shouldEmit(
+  const struct IntervalEventData &listener
+) {
+  if(listener.nextInvoke > internalData) return CustomEventResult::NOTHING;
+  return CustomEventResult::INVOKE;
+}
+
+struct IntervalEventData IntervalEvent::transformData(const float_t &interval) {
+  
+  return {
+    .interval = interval,
+    .nextInvoke = internalData + interval
+  };
+}
+
+struct IntervalEventData IntervalEvent::transformDataAfterEmit(
+  const struct IntervalEventData &listenerData
+) {
+  return {
+    .interval = listenerData.interval,
+    .nextInvoke = listenerData.nextInvoke + listenerData.interval
+  };
+}
+
+void IntervalEvent::tick(const float_t delta) {
+  internalData += delta;
+  emit();
+}
\ No newline at end of file
diff --git a/src/dawn/time/event/IntervalEvent.hpp b/src/dawn/time/event/IntervalEvent.hpp
new file mode 100644
index 00000000..a404efe8
--- /dev/null
+++ b/src/dawn/time/event/IntervalEvent.hpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "event/CustomEvent.hpp"
+
+namespace Dawn {
+  struct IntervalEventData {
+    float_t interval;
+    float_t nextInvoke;
+  };
+
+  class IntervalEvent : public CustomEvent<
+    float_t, struct IntervalEventData, float_t
+  > {
+    protected:
+      enum CustomEventResult shouldEmit(
+        const struct IntervalEventData &listener
+      ) override;
+      struct IntervalEventData transformData(const float_t &interval) override;
+      struct IntervalEventData transformDataAfterEmit(
+        const struct IntervalEventData &listenerData
+      ) override;
+    
+    public:
+      IntervalEvent();
+
+      /**
+       * Ticks this interval event by a time delta amount. Will also emit all
+       * events that are ready to be emitted.
+       * 
+       * @param delta Delta time.
+       */
+      void tick(const float_t delta);
+  };
+}
\ No newline at end of file
diff --git a/src/dawn/time/event/TimeoutEvent.cpp b/src/dawn/time/event/TimeoutEvent.cpp
new file mode 100644
index 00000000..72ce8955
--- /dev/null
+++ b/src/dawn/time/event/TimeoutEvent.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "TimeoutEvent.hpp"
+
+using namespace Dawn;
+
+TimeoutEvent::TimeoutEvent() : CustomEvent() {
+  internalData = 0.0f;
+}
+
+enum CustomEventResult TimeoutEvent::shouldEmit(const float_t &listener) {
+  if(listener > internalData) return CustomEventResult::NOTHING;
+  return CustomEventResult::INVOKE_AND_REMOVE;
+}
+
+float_t TimeoutEvent::transformData(const float_t &interval) {
+  return interval;
+}
+
+void TimeoutEvent::tick(const float_t delta) {
+  internalData += delta;
+  emit();
+}
\ No newline at end of file
diff --git a/src/dawn/time/event/TimeoutEvent.hpp b/src/dawn/time/event/TimeoutEvent.hpp
new file mode 100644
index 00000000..8386dde5
--- /dev/null
+++ b/src/dawn/time/event/TimeoutEvent.hpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "event/CustomEvent.hpp"
+
+namespace Dawn {
+  class TimeoutEvent : public CustomEvent<float_t, float_t, float_t> {
+    protected:
+      enum CustomEventResult shouldEmit(const float_t &listener) override;
+      float_t transformData(const float_t &timeout) override;
+    
+    public:
+      TimeoutEvent();
+
+      /**
+       * Ticks this timeout event by a time delta amount. Will also emit all
+       * events that are ready to be emitted.
+       * 
+       * @param delta Delta time.
+       */
+      void tick(const float_t delta);
+  };
+}
\ No newline at end of file
diff --git a/src/dawnhelloworld/scene/HelloWorldScene.cpp b/src/dawnhelloworld/scene/HelloWorldScene.cpp
index 65c87596..3c2a799e 100644
--- a/src/dawnhelloworld/scene/HelloWorldScene.cpp
+++ b/src/dawnhelloworld/scene/HelloWorldScene.cpp
@@ -23,13 +23,11 @@ void Dawn::helloWorldScene(Scene &s) {
   cubeMesh->createBuffers(CUBE_VERTICE_COUNT, CUBE_INDICE_COUNT);
   CubeMesh::buffer(cubeMesh, glm::vec3(-1, -1, -1), glm::vec3(1, 1, 1), 0, 0);
 
-  auto texture = s.getGame()->assetManager.get<Texture>("texture_border");
-
   auto cubeItem = s.createSceneItem();
   auto cubeMeshRenderer = cubeItem->addComponent<MeshRenderer>();
   cubeMeshRenderer->mesh = cubeMesh;
   auto cubeMaterial = cubeItem->addComponent<SimpleTexturedMaterial>();
-  cubeMaterial->setTexture(texture);
+  cubeMaterial->setTexture(s.getGame()->assetManager.get<Texture>("texture_border"));
 
   addSimpleComponent(cubeItem, [](auto &cmp, auto &events) {
     events.push_back(cmp.getScene()->onUnpausedUpdate.listen([&](float_t delta) {
@@ -37,4 +35,11 @@ void Dawn::helloWorldScene(Scene &s) {
       item->setLocalRotation(item->getLocalRotation() * glm::quat(glm::vec3(1, 1, 0) * delta));
     }));
   });
+
+  addSimpleComponent(cubeItem, [](auto &cmp, auto &events) {
+    events.push_back(cmp.getScene()->onTimeout.listen(5.0f, [&]{
+      auto item = cmp.getItem();
+      item->setLocalPosition(item->getLocalPosition() + glm::vec3(0, 1, 0));
+    }));
+  });
 }
\ No newline at end of file