diff --git a/Cargo.lock b/Cargo.lock
index b2761b0..48a1c02 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2389,8 +2389,10 @@ dependencies = [
  "rbx_mesh",
  "rbx_reflection_database",
  "rbx_xml",
+ "rbxassetid",
  "roblox_emulator",
  "strafesnet_common",
+ "strafesnet_deferred_loader",
 ]
 
 [[package]]
diff --git a/lib/rbx_loader/Cargo.toml b/lib/rbx_loader/Cargo.toml
index 072cabd..9ba6f7b 100644
--- a/lib/rbx_loader/Cargo.toml
+++ b/lib/rbx_loader/Cargo.toml
@@ -18,5 +18,7 @@ rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
 rbx_mesh = "0.1.2"
 rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
 rbx_xml = { version = "0.13.3", registry = "strafesnet" }
+rbxassetid = { version = "0.1.0", path = "../rbxassetid" }
 roblox_emulator = { path = "../roblox_emulator", registry = "strafesnet" }
 strafesnet_common = { path = "../common", registry = "strafesnet" }
+strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader" }
diff --git a/lib/rbx_loader/src/lib.rs b/lib/rbx_loader/src/lib.rs
index bb73961..98a22c6 100644
--- a/lib/rbx_loader/src/lib.rs
+++ b/lib/rbx_loader/src/lib.rs
@@ -3,6 +3,7 @@ use rbx_dom_weak::WeakDom;
 
 mod rbx;
 mod mesh;
+pub mod loader;
 mod primitives;
 
 pub mod data{
@@ -94,14 +95,4 @@ pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
 
 //ConvertError
 
-pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
-	dom:impl AsRef<WeakDom>,
-	acquire_render_config_id:AcquireRenderConfigId,
-	acquire_mesh_id:AcquireMeshId
-)->rbx::PartialMap1
-where
-	AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
-	AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
-{
-	rbx::convert(&dom.as_ref(),acquire_render_config_id,acquire_mesh_id)
-}
+pub use rbx::convert;
diff --git a/lib/rbx_loader/src/loader.rs b/lib/rbx_loader/src/loader.rs
new file mode 100644
index 0000000..099a080
--- /dev/null
+++ b/lib/rbx_loader/src/loader.rs
@@ -0,0 +1,102 @@
+use std::io::Read;
+use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
+use strafesnet_common::model::Mesh;
+use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
+
+use crate::data::RobloxMeshBytes;
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum TextureError{
+	Io(std::io::Error),
+	RobloxAssetIdParse(RobloxAssetIdParseErr),
+}
+impl std::fmt::Display for TextureError{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for TextureError{}
+impl From<std::io::Error> for TextureError{
+	fn from(value:std::io::Error)->Self{
+		Self::Io(value)
+	}
+}
+impl From<RobloxAssetIdParseErr> for TextureError{
+	fn from(value:RobloxAssetIdParseErr)->Self{
+		Self::RobloxAssetIdParse(value)
+	}
+}
+
+pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
+impl TextureLoader<'_>{
+	pub fn new()->Self{
+		Self(std::marker::PhantomData)
+	}
+}
+impl<'a> Loader for TextureLoader<'a>{
+	type Error=TextureError;
+	type Index=&'a str;
+	type Resource=Texture;
+	fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
+		let RobloxAssetId(asset_id)=index.parse()?;
+		let file_name=format!("textures/{}.dds",asset_id);
+		let mut file=std::fs::File::open(file_name)?;
+		let mut data=Vec::new();
+		file.read_to_end(&mut data)?;
+		Ok(Texture::ImageDDS(data))
+	}
+}
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub enum MeshError{
+	Io(std::io::Error),
+	RobloxAssetIdParse(RobloxAssetIdParseErr),
+	Mesh(crate::mesh::Error)
+
+}
+impl std::fmt::Display for MeshError{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for MeshError{}
+impl From<std::io::Error> for MeshError{
+	fn from(value:std::io::Error)->Self{
+		Self::Io(value)
+	}
+}
+impl From<RobloxAssetIdParseErr> for MeshError{
+	fn from(value:RobloxAssetIdParseErr)->Self{
+		Self::RobloxAssetIdParse(value)
+	}
+}
+impl From<crate::mesh::Error> for MeshError{
+	fn from(value:crate::mesh::Error)->Self{
+		Self::Mesh(value)
+	}
+}
+
+pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>);
+impl MeshLoader<'_>{
+	pub fn new()->Self{
+		Self(std::marker::PhantomData)
+	}
+}
+impl<'a> Loader for MeshLoader<'a>{
+	type Error=MeshError;
+	type Index=&'a str;
+	type Resource=Mesh;
+	fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
+		let RobloxAssetId(asset_id)=index.parse()?;
+		let file_name=format!("meshes/{}",asset_id);
+		let mut file=std::fs::File::open(file_name)?;
+		// reading the entire file is way faster than
+		// round tripping to disk every read from the parser
+		let mut data=Vec::new();
+		file.read_to_end(&mut data)?;
+		let mesh=crate::mesh::convert(RobloxMeshBytes::new(data))?;
+		Ok(mesh)
+	}
+}
diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs
index 643cf3d..bd5ff9a 100644
--- a/lib/rbx_loader/src/rbx.rs
+++ b/lib/rbx_loader/src/rbx.rs
@@ -8,6 +8,9 @@ use strafesnet_common::gameplay_attributes as attr;
 use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
 use strafesnet_common::model::RenderConfigId;
 use strafesnet_common::updatable::Updatable;
+use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader};
+use strafesnet_deferred_loader::mesh::Meshes;
+use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
 
 fn class_is_a(class: &str, superclass: &str) -> bool {
 	if class==superclass {
@@ -432,23 +435,18 @@ struct GetAttributesArgs{
 	can_collide:bool,
 	velocity:Planar64Vec3,
 }
-pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
-	dom:&rbx_dom_weak::WeakDom,
-	mut acquire_render_config_id:AcquireRenderConfigId,
-	mut acquire_mesh_id:AcquireMeshId,
-)->PartialMap1
-where
-	AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
-	AcquireMeshId:FnMut(&str)->model::MeshId,
-{
-
+pub fn convert<'a>(
+	dom:&'a rbx_dom_weak::WeakDom,
+	render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
+	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
+)->PartialMap1{
 	let mut deferred_models_deferred_attributes=Vec::new();
 	let mut primitive_models_deferred_attributes=Vec::new();
 	let mut primitive_meshes=Vec::new();
 	let mut mesh_id_from_description=HashMap::new();
 
 	//just going to leave it like this for now instead of reworking the data structures for this whole thing
-	let textureless_render_group=acquire_render_config_id(None);
+	let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None);
 
 	let mut object_refs=Vec::new();
 	let mut temp_objects=Vec::new();
@@ -529,7 +527,7 @@ where
 							decal.properties.get("Color3"),
 							decal.properties.get("Transparency"),
 						) {
-							let render_id=acquire_render_config_id(Some(content.as_ref()));
+							let render_id=render_config_deferred_loader.acquire_render_config_id(Some(content.as_ref()));
 								let normal_id=normalid.to_u32();
 								if normal_id<6{
 									let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
@@ -691,8 +689,8 @@ where
 						object.properties.get("TextureID"),
 					){
 						(
-							MeshAvailability::Deferred(acquire_render_config_id(Some(texture_asset_id.as_ref()))),
-							acquire_mesh_id(mesh_asset_id.as_ref()),
+							MeshAvailability::Deferred(render_config_deferred_loader.acquire_render_config_id(Some(texture_asset_id.as_ref()))),
+							mesh_deferred_loader.acquire_mesh_id(mesh_asset_id.as_ref()),
 						)
 					}else{
 						panic!("Mesh has no Mesh or Texture");
@@ -736,7 +734,7 @@ pub struct PartialMap1{
 impl PartialMap1{
 	pub fn add_meshpart_meshes_and_calculate_attributes(
 		mut self,
-		meshpart_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::RobloxMeshBytes)>,
+		meshpart_meshes:Meshes,
 	)->PartialMap2{
 		//calculate attributes
 		let mut modes_builder=ModesBuilder::default();
@@ -749,24 +747,16 @@ impl PartialMap1{
 		//decode roblox meshes
 		//generate mesh_id_map based on meshes that failed to load
 		let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
-		meshpart_meshes.into_iter().flat_map(|(old_mesh_id,roblox_mesh_bytes)|
-			match crate::mesh::convert(roblox_mesh_bytes){
-				Ok(mesh)=>{
-					let mut aabb=strafesnet_common::aabb::Aabb::default();
-					for &pos in &mesh.unique_pos{
-						aabb.grow(pos);
-					}
-					Some((old_mesh_id,MeshWithAabb{
-						mesh,
-						aabb,
-					}))
-				},
-				Err(e)=>{
-					println!("Error converting mesh: {e:?}");
-					None
-				},
+		meshpart_meshes.consume().map(|(old_mesh_id,mesh)|{
+			let mut aabb=strafesnet_common::aabb::Aabb::default();
+			for &pos in &mesh.unique_pos{
+				aabb.grow(pos);
 			}
-		).collect();
+			(old_mesh_id,MeshWithAabb{
+				mesh,
+				aabb,
+			})
+		}).collect();
 
 		let mut mesh_id_from_render_config_id=HashMap::new();
 		//ignore meshes that fail to load completely for now
@@ -879,11 +869,11 @@ pub struct PartialMap2{
 impl PartialMap2{
 	pub fn add_render_configs_and_textures(
 		self,
-		render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
-		textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
+		render_configs:RenderConfigs,
 	)->map::CompleteMap{
+		let (textures,render_configs)=render_configs.consume();
 		let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
-		=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
+		=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
 			(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
 		}).unzip();
 		let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{