AssetCache.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Reflection;
  30. using System.Text.RegularExpressions;
  31. using System.Threading;
  32. using GlynnTucker.Cache;
  33. using log4net;
  34. using OpenMetaverse;
  35. using OpenMetaverse.Packets;
  36. using OpenSim.Framework.Statistics;
  37. using System.Text;
  38. namespace OpenSim.Framework.Communications.Cache
  39. {
  40. /// <summary>
  41. /// Manages local cache of assets and their sending to viewers.
  42. /// </summary>
  43. ///
  44. /// This class actually encapsulates two largely separate mechanisms. One mechanism fetches assets either
  45. /// synchronously or async and passes the data back to the requester. The second mechanism fetches assets and
  46. /// sends packetised data directly back to the client. The only point where they meet is AssetReceived() and
  47. /// AssetNotFound(), which means they do share the same asset and texture caches.
  48. public class AssetCache : IAssetCache
  49. {
  50. private static readonly ILog m_log
  51. = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  52. protected ICache m_memcache = new SimpleMemoryCache();
  53. /// <value>
  54. /// Assets requests which are waiting for asset server data. This includes texture requests
  55. /// </value>
  56. private Dictionary<UUID, AssetRequest> RequestedAssets;
  57. /// <value>
  58. /// Asset requests with data which are ready to be sent back to requesters. This includes textures.
  59. /// </value>
  60. private List<AssetRequest> AssetRequests;
  61. /// <value>
  62. /// Until the asset request is fulfilled, each asset request is associated with a list of requesters
  63. /// </value>
  64. private Dictionary<UUID, AssetRequestsList> RequestLists;
  65. #region IPlugin
  66. /// <summary>
  67. /// The methods and properties in this section are needed to
  68. /// support the IPlugin interface. They cann all be overridden
  69. /// as needed by a derived class.
  70. /// </summary>
  71. public virtual string Name
  72. {
  73. get { return "OpenSim.Framework.Communications.Cache.AssetCache"; }
  74. }
  75. public virtual string Version
  76. {
  77. get { return "1.0"; }
  78. }
  79. public virtual void Initialise()
  80. {
  81. m_log.Debug("[ASSET CACHE]: Asset cache null initialisation");
  82. }
  83. public virtual void Initialise(IAssetServer assetServer)
  84. {
  85. m_log.Debug("[ASSET CACHE]: Asset cache server-specified initialisation");
  86. m_log.InfoFormat("[ASSET CACHE]: Asset cache initialisation [{0}/{1}]", Name, Version);
  87. Reset();
  88. m_assetServer = assetServer;
  89. m_assetServer.SetReceiver(this);
  90. Thread assetCacheThread = new Thread(RunAssetManager);
  91. assetCacheThread.Name = "AssetCacheThread";
  92. assetCacheThread.IsBackground = true;
  93. assetCacheThread.Start();
  94. ThreadTracker.Add(assetCacheThread);
  95. }
  96. public virtual void Initialise(ConfigSettings settings, IAssetServer assetServer)
  97. {
  98. m_log.Debug("[ASSET CACHE]: Asset cache configured initialisation");
  99. Initialise(assetServer);
  100. }
  101. public void Dispose()
  102. {
  103. }
  104. #endregion
  105. public IAssetServer AssetServer
  106. {
  107. get { return m_assetServer; }
  108. }
  109. private IAssetServer m_assetServer;
  110. public AssetCache()
  111. {
  112. m_log.Debug("[ASSET CACHE]: Asset cache (plugin constructor)");
  113. }
  114. /// <summary>
  115. /// Constructor.
  116. /// </summary>
  117. /// <param name="assetServer"></param>
  118. public AssetCache(IAssetServer assetServer)
  119. {
  120. Initialise(assetServer);
  121. }
  122. public void ShowState()
  123. {
  124. m_log.InfoFormat("Memcache:{0} RequestLists:{1}",
  125. m_memcache.Count,
  126. // AssetRequests.Count,
  127. // RequestedAssets.Count,
  128. RequestLists.Count);
  129. }
  130. public void Clear()
  131. {
  132. m_log.Info("[ASSET CACHE]: Clearing Asset cache");
  133. if (StatsManager.SimExtraStats != null)
  134. StatsManager.SimExtraStats.ClearAssetCacheStatistics();
  135. Reset();
  136. }
  137. /// <summary>
  138. /// Reset the cache.
  139. /// </summary>
  140. private void Reset()
  141. {
  142. AssetRequests = new List<AssetRequest>();
  143. RequestedAssets = new Dictionary<UUID, AssetRequest>();
  144. RequestLists = new Dictionary<UUID, AssetRequestsList>();
  145. }
  146. /// <summary>
  147. /// Process the asset queue which holds data which is packeted up and sent
  148. /// directly back to the client.
  149. /// </summary>
  150. private void RunAssetManager()
  151. {
  152. while (true)
  153. {
  154. try
  155. {
  156. ProcessAssetQueue();
  157. Thread.Sleep(500);
  158. }
  159. catch (Exception e)
  160. {
  161. if (e != null)
  162. {
  163. m_log.ErrorFormat("[ASSET CACHE]: {0}", e);
  164. }
  165. else
  166. {
  167. // this looks weird, but we've seen this show up as an issue in unit tests, so leave it here until we know why
  168. m_log.Error("[ASSET CACHE]: an exception was thrown in RunAssetManager, but is now null. Something is very wrong.");
  169. }
  170. }
  171. }
  172. }
  173. public bool TryGetCachedAsset(UUID assetId, out AssetBase asset)
  174. {
  175. Object tmp;
  176. if (m_memcache.TryGet(assetId, out tmp))
  177. {
  178. asset = (AssetBase)tmp;
  179. //m_log.Info("Retrieved from cache " + assetId);
  180. return true;
  181. }
  182. asset = null;
  183. return false;
  184. }
  185. public void GetAsset(UUID assetId, AssetRequestCallback callback, bool isTexture)
  186. {
  187. //m_log.DebugFormat("[ASSET CACHE]: Requesting {0} {1}", isTexture ? "texture" : "asset", assetId);
  188. // Xantor 20080526:
  189. // if a request is made for an asset which is not in the cache yet, but has already been requested by
  190. // something else, queue up the callbacks on that requestor instead of swamping the assetserver
  191. // with multiple requests for the same asset.
  192. AssetBase asset;
  193. if (TryGetCachedAsset(assetId, out asset))
  194. {
  195. callback(assetId, asset);
  196. }
  197. else
  198. {
  199. // m_log.DebugFormat("[ASSET CACHE]: Adding request for {0} {1}", isTexture ? "texture" : "asset", assetId);
  200. NewAssetRequest req = new NewAssetRequest(callback);
  201. AssetRequestsList requestList;
  202. lock (RequestLists)
  203. {
  204. if (RequestLists.TryGetValue(assetId, out requestList)) // do we already have a request pending?
  205. {
  206. // m_log.DebugFormat("[ASSET CACHE]: Intercepted Duplicate request for {0} {1}", isTexture ? "texture" : "asset", assetId);
  207. // add to callbacks for this assetId
  208. RequestLists[assetId].Requests.Add(req);
  209. }
  210. else
  211. {
  212. // m_log.DebugFormat("[ASSET CACHE]: Adding request for {0} {1}", isTexture ? "texture" : "asset", assetId);
  213. requestList = new AssetRequestsList();
  214. requestList.TimeRequested = DateTime.Now;
  215. requestList.Requests.Add(req);
  216. RequestLists.Add(assetId, requestList);
  217. m_assetServer.RequestAsset(assetId, isTexture);
  218. }
  219. }
  220. }
  221. }
  222. public AssetBase GetAsset(UUID assetID, bool isTexture)
  223. {
  224. // I'm not going over 3 seconds since this will be blocking processing of all the other inbound
  225. // packets from the client.
  226. const int pollPeriod = 200;
  227. int maxPolls = 15;
  228. AssetBase asset;
  229. if (TryGetCachedAsset(assetID, out asset))
  230. {
  231. return asset;
  232. }
  233. m_assetServer.RequestAsset(assetID, isTexture);
  234. do
  235. {
  236. Thread.Sleep(pollPeriod);
  237. if (TryGetCachedAsset(assetID, out asset))
  238. {
  239. return asset;
  240. }
  241. }
  242. while (--maxPolls > 0);
  243. m_log.WarnFormat("[ASSET CACHE]: {0} {1} was not received before the retrieval timeout was reached",
  244. isTexture ? "texture" : "asset", assetID.ToString());
  245. return null;
  246. }
  247. public void AddAsset(AssetBase asset)
  248. {
  249. if (!m_memcache.Contains(asset.FullID))
  250. {
  251. m_log.Info("[CACHE] Caching " + asset.FullID + " for 24 hours from last access");
  252. // Use 24 hour rolling asset cache.
  253. m_memcache.AddOrUpdate(asset.FullID, asset, TimeSpan.FromHours(24));
  254. // According to http://wiki.secondlife.com/wiki/AssetUploadRequest, Local signifies that the
  255. // information is stored locally. It could disappear, in which case we could send the
  256. // ImageNotInDatabase packet to tell the client this.
  257. //
  258. // However, this doesn't quite appear to work with local textures that are part of an avatar's
  259. // appearance texture set. Whilst sending an ImageNotInDatabase does trigger an automatic rebake
  260. // and reupload by the client, if those assets aren't pushed to the asset server anyway, then
  261. // on crossing onto another region server, other avatars can no longer get the required textures.
  262. // There doesn't appear to be any signal from the sim to the newly region border crossed client
  263. // asking it to reupload its local texture assets to that region server.
  264. //
  265. // One can think of other cunning ways around this. For instance, on a region crossing or teleport,
  266. // the original sim could squirt local assets to the new sim. Or the new sim could have pointers
  267. // to the original sim to fetch the 'local' assets (this is getting more complicated).
  268. //
  269. // But for now, we're going to take the easy way out and store local assets globally.
  270. //
  271. // TODO: Also, Temporary is now deprecated. We should start ignoring it and not passing it out from LLClientView.
  272. if (!asset.Temporary || asset.Local)
  273. {
  274. m_assetServer.StoreAsset(asset);
  275. }
  276. if (StatsManager.SimExtraStats != null)
  277. StatsManager.SimExtraStats.AddAsset(asset);
  278. }
  279. }
  280. public void ExpireAsset(UUID uuid)
  281. {
  282. if (m_memcache.Contains(uuid))
  283. {
  284. m_memcache.Remove(uuid);
  285. if (StatsManager.SimExtraStats != null)
  286. StatsManager.SimExtraStats.RemoveAsset(uuid);
  287. }
  288. }
  289. // See IAssetReceiver
  290. public virtual void AssetReceived(AssetBase asset, bool IsTexture)
  291. {
  292. AssetInfo assetInf = new AssetInfo(asset);
  293. ProcessReceivedAsset(IsTexture, assetInf, null);
  294. if (!m_memcache.Contains(assetInf.FullID))
  295. {
  296. m_memcache.AddOrUpdate(assetInf.FullID, assetInf, TimeSpan.FromHours(24));
  297. if (StatsManager.SimExtraStats != null)
  298. StatsManager.SimExtraStats.AddAsset(assetInf);
  299. if (RequestedAssets.ContainsKey(assetInf.FullID))
  300. {
  301. AssetRequest req = RequestedAssets[assetInf.FullID];
  302. req.AssetInf = assetInf;
  303. req.NumPackets = CalculateNumPackets(assetInf.Data);
  304. RequestedAssets.Remove(assetInf.FullID);
  305. if (req.AssetRequestSource == 2 && assetInf.Type == 10)
  306. {
  307. // If it's a direct request for a script, drop it
  308. // because it's a hacked client
  309. }
  310. else
  311. {
  312. lock (AssetRequests)
  313. {
  314. AssetRequests.Add(req);
  315. }
  316. }
  317. }
  318. }
  319. // Notify requesters for this asset
  320. AssetRequestsList reqList;
  321. lock (RequestLists)
  322. {
  323. if (RequestLists.TryGetValue(asset.FullID, out reqList))
  324. RequestLists.Remove(asset.FullID);
  325. }
  326. if (reqList != null)
  327. {
  328. if (StatsManager.SimExtraStats != null)
  329. StatsManager.SimExtraStats.AddAssetRequestTimeAfterCacheMiss(DateTime.Now - reqList.TimeRequested);
  330. foreach (NewAssetRequest req in reqList.Requests)
  331. {
  332. // Xantor 20080526 are we really calling all the callbacks if multiple queued for 1 request? -- Yes, checked
  333. // m_log.DebugFormat("[ASSET CACHE]: Callback for asset {0}", asset.FullID);
  334. req.Callback(asset.FullID, asset);
  335. }
  336. }
  337. }
  338. protected void ProcessReceivedAsset(bool IsTexture, AssetInfo assetInf, IUserService userService)
  339. {
  340. // if (!IsTexture && assetInf.ContainsReferences && false)
  341. // {
  342. // assetInf.Data = ProcessAssetData(assetInf.Data, userService);
  343. // }
  344. }
  345. // See IAssetReceiver
  346. public virtual void AssetNotFound(UUID assetId, bool isTexture)
  347. {
  348. // m_log.WarnFormat("[ASSET CACHE]: AssetNotFound for {0}", assetId);
  349. // Remember the fact that this asset could not be found to prevent delays from repeated requests
  350. m_memcache.Add(assetId, null, TimeSpan.FromHours(24));
  351. // Notify requesters for this asset
  352. AssetRequestsList reqList;
  353. lock (RequestLists)
  354. {
  355. if (RequestLists.TryGetValue(assetId, out reqList))
  356. RequestLists.Remove(assetId);
  357. }
  358. if (reqList != null)
  359. {
  360. if (StatsManager.SimExtraStats != null)
  361. StatsManager.SimExtraStats.AddAssetRequestTimeAfterCacheMiss(DateTime.Now - reqList.TimeRequested);
  362. foreach (NewAssetRequest req in reqList.Requests)
  363. {
  364. req.Callback(assetId, null);
  365. }
  366. }
  367. }
  368. /// <summary>
  369. /// Calculate the number of packets required to send the asset to the client.
  370. /// </summary>
  371. /// <param name="data"></param>
  372. /// <returns></returns>
  373. private static int CalculateNumPackets(byte[] data)
  374. {
  375. const uint m_maxPacketSize = 600;
  376. int numPackets = 1;
  377. if (data.LongLength > m_maxPacketSize)
  378. {
  379. // over max number of bytes so split up file
  380. long restData = data.LongLength - m_maxPacketSize;
  381. int restPackets = (int)((restData + m_maxPacketSize - 1) / m_maxPacketSize);
  382. numPackets += restPackets;
  383. }
  384. return numPackets;
  385. }
  386. public void AddAssetRequest(IClientAPI userInfo, TransferRequestPacket transferRequest)
  387. {
  388. UUID requestID = UUID.Zero;
  389. byte source = 2;
  390. if (transferRequest.TransferInfo.SourceType == 2)
  391. {
  392. //direct asset request
  393. requestID = new UUID(transferRequest.TransferInfo.Params, 0);
  394. }
  395. else if (transferRequest.TransferInfo.SourceType == 3)
  396. {
  397. //inventory asset request
  398. requestID = new UUID(transferRequest.TransferInfo.Params, 80);
  399. source = 3;
  400. //m_log.Debug("asset request " + requestID);
  401. }
  402. //check to see if asset is in local cache, if not we need to request it from asset server.
  403. //m_log.Debug("asset request " + requestID);
  404. if (!m_memcache.Contains(requestID))
  405. {
  406. //not found asset
  407. // so request from asset server
  408. if (!RequestedAssets.ContainsKey(requestID))
  409. {
  410. AssetRequest request = new AssetRequest();
  411. request.RequestUser = userInfo;
  412. request.RequestAssetID = requestID;
  413. request.TransferRequestID = transferRequest.TransferInfo.TransferID;
  414. request.AssetRequestSource = source;
  415. request.Params = transferRequest.TransferInfo.Params;
  416. RequestedAssets.Add(requestID, request);
  417. m_assetServer.RequestAsset(requestID, false);
  418. }
  419. return;
  420. }
  421. // It has an entry in our cache
  422. AssetBase asset = (AssetBase)m_memcache[requestID];
  423. // FIXME: We never tell the client about assets which do not exist when requested by this transfer mechanism, which can't be right.
  424. if (null == asset)
  425. {
  426. //m_log.DebugFormat("[ASSET CACHE]: Asset transfer request for asset which is {0} already known to be missing. Dropping", requestID);
  427. return;
  428. }
  429. // Scripts cannot be retrieved by direct request
  430. if (transferRequest.TransferInfo.SourceType == 2 && asset.Type == 10)
  431. return;
  432. // The asset is knosn to exist and is in our cache, so add it to the AssetRequests list
  433. AssetRequest req = new AssetRequest();
  434. req.RequestUser = userInfo;
  435. req.RequestAssetID = requestID;
  436. req.TransferRequestID = transferRequest.TransferInfo.TransferID;
  437. req.AssetRequestSource = source;
  438. req.Params = transferRequest.TransferInfo.Params;
  439. req.AssetInf = new AssetInfo(asset);
  440. req.NumPackets = CalculateNumPackets(asset.Data);
  441. lock (AssetRequests)
  442. {
  443. AssetRequests.Add(req);
  444. }
  445. }
  446. /// <summary>
  447. /// Process the asset queue which sends packets directly back to the client.
  448. /// </summary>
  449. private void ProcessAssetQueue()
  450. {
  451. //should move the asset downloading to a module, like has been done with texture downloading
  452. if (AssetRequests.Count == 0)
  453. {
  454. //no requests waiting
  455. return;
  456. }
  457. // if less than 5, do all of them
  458. int num = Math.Min(5, AssetRequests.Count);
  459. AssetRequest req;
  460. AssetRequestToClient req2 = new AssetRequestToClient();
  461. lock (AssetRequests)
  462. {
  463. for (int i = 0; i < num; i++)
  464. {
  465. req = AssetRequests[0];
  466. AssetRequests.RemoveAt(0);
  467. req2.AssetInf = req.AssetInf;
  468. req2.AssetRequestSource = req.AssetRequestSource;
  469. req2.DataPointer = req.DataPointer;
  470. req2.DiscardLevel = req.DiscardLevel;
  471. req2.ImageInfo = req.ImageInfo;
  472. req2.IsTextureRequest = req.IsTextureRequest;
  473. req2.NumPackets = req.NumPackets;
  474. req2.PacketCounter = req.PacketCounter;
  475. req2.Params = req.Params;
  476. req2.RequestAssetID = req.RequestAssetID;
  477. req2.TransferRequestID = req.TransferRequestID;
  478. req.RequestUser.SendAsset(req2);
  479. }
  480. }
  481. }
  482. public byte[] ProcessAssetData(byte[] assetData, IUserService userService)
  483. {
  484. string data = Encoding.ASCII.GetString(assetData);
  485. data = ProcessAssetDataString(data, userService);
  486. return Encoding.ASCII.GetBytes(data);
  487. }
  488. public string ProcessAssetDataString(string data, IUserService userService)
  489. {
  490. Regex regex = new Regex("(creator_url|owner_url)\\s+(\\S+)");
  491. data = regex.Replace(data, delegate(Match m)
  492. {
  493. string result = String.Empty;
  494. string key = m.Groups[1].Captures[0].Value;
  495. string value = m.Groups[2].Captures[0].Value;
  496. Uri userUri;
  497. switch (key)
  498. {
  499. case "creator_url":
  500. userUri = new Uri(value);
  501. result = "creator_id " + ResolveUserUri(userService, userUri);
  502. break;
  503. case "owner_url":
  504. userUri = new Uri(value);
  505. result = "owner_id " + ResolveUserUri(userService, userUri);
  506. break;
  507. }
  508. return result;
  509. });
  510. return data;
  511. }
  512. private Guid ResolveUserUri(IUserService userService, Uri userUri)
  513. {
  514. Guid id;
  515. UserProfileData userProfile = userService.GetUserProfile(userUri);
  516. if (userProfile == null)
  517. {
  518. id = Guid.Empty;
  519. }
  520. else
  521. {
  522. id = userProfile.ID.Guid;
  523. }
  524. return id;
  525. }
  526. public class AssetRequest
  527. {
  528. public IClientAPI RequestUser;
  529. public UUID RequestAssetID;
  530. public AssetInfo AssetInf;
  531. public TextureImage ImageInfo;
  532. public UUID TransferRequestID;
  533. public long DataPointer = 0;
  534. public int NumPackets = 0;
  535. public int PacketCounter = 0;
  536. public bool IsTextureRequest;
  537. public byte AssetRequestSource = 2;
  538. public byte[] Params = null;
  539. //public bool AssetInCache;
  540. //public int TimeRequested;
  541. public int DiscardLevel = -1;
  542. }
  543. public class AssetInfo : AssetBase
  544. {
  545. public AssetInfo(AssetBase aBase)
  546. {
  547. Data = aBase.Data;
  548. FullID = aBase.FullID;
  549. Type = aBase.Type;
  550. Name = aBase.Name;
  551. Description = aBase.Description;
  552. }
  553. public const string Secret = "secret";
  554. }
  555. public class TextureImage : AssetBase
  556. {
  557. public TextureImage(AssetBase aBase)
  558. {
  559. Data = aBase.Data;
  560. FullID = aBase.FullID;
  561. Type = aBase.Type;
  562. Name = aBase.Name;
  563. Description = aBase.Description;
  564. }
  565. }
  566. /// <summary>
  567. /// A list of requests for a particular asset.
  568. /// </summary>
  569. public class AssetRequestsList
  570. {
  571. /// <summary>
  572. /// A list of requests for assets
  573. /// </summary>
  574. public List<NewAssetRequest> Requests = new List<NewAssetRequest>();
  575. /// <summary>
  576. /// Record the time that this request was first made.
  577. /// </summary>
  578. public DateTime TimeRequested;
  579. }
  580. /// <summary>
  581. /// Represent a request for an asset that has yet to be fulfilled.
  582. /// </summary>
  583. public class NewAssetRequest
  584. {
  585. public AssetRequestCallback Callback;
  586. public NewAssetRequest(AssetRequestCallback callback)
  587. {
  588. Callback = callback;
  589. }
  590. }
  591. }
  592. }