1 module served.backend.lazy_workspaced;
2 
3 import std.algorithm;
4 import std.experimental.logger;
5 
6 import workspaced.api;
7 import workspaced.backend;
8 
9 alias LazyLoadHook = void delegate() nothrow;
10 alias LazyLoadHooks = LazyLoadHook[];
11 
12 alias InstanceLoadHook = void delegate();
13 
14 class LazyWorkspaceD : WorkspaceD
15 {
16 	static class LazyInstance : WorkspaceD.Instance
17 	{
18 		private LazyWorkspaceD backend;
19 		private LazyLoadHooks[string] lazyLoadCallbacks;
20 		private InstanceLoadHook[] accessCallbacks;
21 		private bool wasAccessed;
22 		ComponentFactory[] lazyComponents;
23 
24 		bool didCallAccess() const
25 		{
26 			return wasAccessed;
27 		}
28 
29 		void onLazyLoad(string component, LazyLoadHook hook)
30 		{
31 			foreach (com; instanceComponents)
32 				if (com.info.name == component)
33 					return hook();
34 
35 			lazyLoadCallbacks.require(component) ~= hook;
36 		}
37 
38 		void onLazyLoadInstance(InstanceLoadHook hook)
39 		{
40 			if (wasAccessed)
41 				return hook();
42 			else
43 				accessCallbacks ~= hook;
44 		}
45 
46 		override void onBeforeAccessComponent(
47 				ComponentInfo info) const
48 		{
49 			accessCheck();
50 
51 			// lots of const-remove-casts because lazy loading should in theory
52 			// not break anything constness related
53 			foreach (i, com; lazyComponents)
54 				if (com.info.name == info.name)
55 				{
56 					trace("Lazy loading component ",
57 							info.name);
58 
59 					Exception error;
60 					auto wrap = (cast() com).create(cast() backend,
61 							cast() this, error);
62 					if (wrap)
63 						(cast() this).attachComponent(
64 								ComponentWrapperInstance(wrap,
65 								com.info));
66 					else if (backend.onBindFail)
67 						backend.onBindFail(cast() this,
68 								cast() com, error);
69 					return;
70 				}
71 
72 			super.onBeforeAccessComponent(info);
73 		}
74 
75 		override bool checkHasComponent(ComponentInfo info) const nothrow
76 		{
77 			try
78 			{
79 				accessCheck();
80 			}
81 			catch (Exception)
82 			{
83 				return false;
84 			}
85 
86 			foreach (com; lazyComponents)
87 				if (com.info.name == info.name)
88 					return true;
89 
90 			return super.checkHasComponent(info);
91 		}
92 
93 		void attachLazy(ComponentFactory factory)
94 		{
95 			lazyComponents ~= factory;
96 		}
97 
98 		override bool attach(WorkspaceD workspaced,
99 				ComponentInfo info)
100 		{
101 			foreach (factory; workspaced.components)
102 			{
103 				if (factory.info.name == info.name)
104 				{
105 					attachLazy(factory);
106 					return true;
107 				}
108 			}
109 			throw new Exception("Component not found");
110 		}
111 
112 		protected override void attachComponent(
113 				ComponentWrapperInstance component)
114 		{
115 			accessCheck();
116 
117 			lazyComponents = lazyComponents.remove!(
118 					a => a.info.name == component.info.name);
119 			instanceComponents ~= component;
120 
121 			auto hooks = lazyLoadCallbacks.get(component.info.name, null);
122 			lazyLoadCallbacks.remove(component.info.name);
123 			foreach (hook; hooks)
124 				hook();
125 		}
126 
127 		protected void accessCheck() const
128 		{
129 			if (!wasAccessed)
130 			{
131 				cast()wasAccessed = true;
132 				try
133 				{
134 					trace("attaching cwd ", cwd);
135 
136 					foreach (hook; accessCallbacks)
137 						hook();
138 
139 					cast()accessCallbacks = null;
140 				}
141 				catch (Exception e)
142 				{
143 					error("failed attaching project: ", e);
144 					throw e;
145 				}
146 			}
147 		}
148 	}
149 
150 	override void onBeforeAccessGlobalComponent(
151 			ComponentInfo info) const
152 	{
153 
154 	}
155 
156 	override bool checkHasGlobalComponent(ComponentInfo info) const
157 	{
158 		return super.checkHasGlobalComponent(info);
159 	}
160 
161 	protected override Instance createInstance(
162 			string cwd, Configuration config)
163 	{
164 		auto inst = new LazyInstance();
165 		inst.cwd = cwd;
166 		inst.config = config;
167 		inst.backend = this;
168 		return inst;
169 	}
170 
171 	protected override void autoRegisterComponents(
172 			Instance inst)
173 	{
174 		auto lazyInstance = cast(LazyInstance) inst;
175 		if (!lazyInstance)
176 			return super.autoRegisterComponents(inst);
177 
178 		foreach (factory; components)
179 		{
180 			if (factory.autoRegister)
181 			{
182 				lazyInstance.attachLazy(factory);
183 			}
184 		}
185 	}
186 
187 	override void onRegisterComponent(
188 			ref ComponentFactory factory, bool autoRegister)
189 	{
190 		components ~= ComponentFactoryInstance(factory, autoRegister);
191 		auto info = factory.info;
192 		Exception error;
193 		auto glob = factory.create(this, null, error);
194 		if (glob)
195 			globalComponents ~= ComponentWrapperInstance(glob, info);
196 		else if (onBindFail)
197 			onBindFail(null, factory, error);
198 
199 		if (autoRegister)
200 			foreach (ref instance; instances)
201 			{
202 				auto lazyInstance = cast(LazyInstance) instance;
203 				if (lazyInstance)
204 					lazyInstance.attachLazy(factory);
205 				else
206 				{
207 					auto inst = factory.create(this,
208 							instance, error);
209 					if (inst)
210 						instance.attachComponent(ComponentWrapperInstance(inst,
211 								factory.info));
212 					else if (onBindFail)
213 						onBindFail(instance, factory, error);
214 				}
215 			}
216 	}
217 
218 	override bool attach(Instance instance,
219 			string component, out Exception error)
220 	{
221 		auto lazyInstance = cast(LazyInstance) instance;
222 		if (!lazyInstance)
223 			return super.attach(instance, component, error);
224 
225 		foreach (factory; components)
226 		{
227 			if (factory.info.name == component)
228 			{
229 				lazyInstance.attachLazy(factory);
230 				return true;
231 			}
232 		}
233 		return false;
234 	}
235 
236 	bool attachEager(Instance instance,
237 			string component, out Exception error)
238 	{
239 		return super.attach(instance, component, error);
240 	}
241 }