/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copy = new StationaryGrhData(ContentManager, newGrhIndex, newCategorization, TextureName, AutomaticSize ? (Rectangle?)null : SourceRect); return(copy); }
/// <summary> /// Creates the <see cref="StationaryGrhData"/> frames for this <see cref="AutomaticAnimatedGrhData"/>. /// </summary> /// <param name="directory">The directory containing the frames.</param> /// <returns>An array of the <see cref="StationaryGrhData"/> frames.</returns> StationaryGrhData[] CreateFrames(string directory) { // Find all the valid content files var allFiles = Directory.GetFiles(directory, "*" + ContentPaths.ContentFileSuffix, SearchOption.TopDirectoryOnly); // Filter out the files with invalid naming conventions, and sort them so we animate in the correct order var files = SortAndFilterFrameFiles(allFiles); // Build the individual frames var frames = new StationaryGrhData[files.Length]; for (var i = 0; i < frames.Length; i++) { var contentAssetName = ContentAssetName.FromAbsoluteFilePath(files[i], ContentPaths.Build.Grhs).Value; var textureAssetName = new TextureAssetName(contentAssetName); var frameGrhData = new StationaryGrhData(this, textureAssetName); frames[i] = frameGrhData; } if (frames.Length == 0) { throw new Exception("Animation has no frames"); } return(frames); }
public static void Load(ContentPaths contentPath, IContentManager cm) { if (IsLoaded) { return; } _isLoaded = true; var path = GetGrhDataFilePath(contentPath); if (cm == null) { throw new ArgumentNullException("cm"); } if (!File.Exists(path)) { throw new FileNotFoundException("GrhData data file not found.", path); } _isLoading = true; try { // Create the GrhData DArray if (_grhDatas == null) { _grhDatas = new DArray <GrhData>(256); } else { _grhDatas.Clear(); } _catDic.Clear(); _grhDatas.ItemAdded -= AddHandler; _grhDatas.ItemAdded += AddHandler; _grhDatas.ItemRemoved -= RemoveHandler; _grhDatas.ItemRemoved += RemoveHandler; // Read and add the GrhDatas in order by their type var reader = GenericValueReader.CreateFromFile(path, _rootNodeName); LoadGrhDatas(reader, _nonAnimatedGrhDatasNodeName, x => StationaryGrhData.Read(x, cm)); LoadGrhDatas(reader, _animatedGrhDatasNodeName, AnimatedGrhData.Read); LoadGrhDatas(reader, _autoAnimatedGrhDatasNodeName, x => AutomaticAnimatedGrhData.Read(x, cm)); // Trim down the GrhData array, mainly for the client since it will never add/remove any GrhDatas // while in the Client, and the Client is what counts, baby! _grhDatas.Trim(); } finally { _isLoading = false; } }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copyArray = new StationaryGrhData[_frames.Length]; Array.Copy(_frames, copyArray, _frames.Length); var copy = new AnimatedGrhData(newGrhIndex, newCategorization) { _frames = copyArray }; copy.SetSpeed(Speed); return(copy); }
StationaryGrhData[] CreateFrames(IList <GrhIndex> frameIndices) { var frames = new StationaryGrhData[frameIndices.Count]; for (var i = 0; i < frameIndices.Count; i++) { frames[i] = GrhInfo.GetData(frameIndices[i]) as StationaryGrhData; if (frames[i] == null) { const string errmsg = "Failed to load GrhData `{0}`. GrhData `{1}` needs it for frame index `{2}` (0-based), out of `{3}` frames total."; var err = string.Format(errmsg, frames[i], this, i, frameIndices.Count); throw new GrhDataException(this, err); } } return(frames); }
/// <summary> /// Replaces an existing GrhData with a new <see cref="StationaryGrhData"/>. /// </summary> /// <param name="grhIndex">The index of the <see cref="GrhData"/> to convert.</param> /// <returns>The created <see cref="StationaryGrhData"/>, or null if the replacement failed.</returns> public static StationaryGrhData ReplaceExistingWithStationary(GrhIndex grhIndex) { var gd = GetData(grhIndex); if (gd == null) { return(null); } if (gd is StationaryGrhData) { return((StationaryGrhData)gd); } var newGD = new StationaryGrhData(GetContentManager(), gd.GrhIndex, gd.Categorization, null, null); Delete(gd); AddGrhData(newGD); return(newGD); }
static StationaryGrhData CreateGrhData(GrhIndex grhIndex, IContentManager contentManager, SpriteCategorization categorization, string texture, Vector2?pos, Vector2?size) { Debug.Assert(!grhIndex.IsInvalid); Rectangle?source; if (pos == null || size == null) { source = null; } else { source = new Rectangle(pos.Value.X, pos.Value.Y, size.Value.X, size.Value.Y); } var gd = new StationaryGrhData(contentManager, grhIndex, categorization, texture, source); AddGrhData(gd); return(gd); }
/// <summary> /// Creates the <see cref="StationaryGrhData"/> frames for this <see cref="AutomaticAnimatedGrhData"/>. /// </summary> /// <param name="directory">The directory containing the frames.</param> /// <returns>An array of the <see cref="StationaryGrhData"/> frames.</returns> StationaryGrhData[] CreateFrames(string directory) { // Find all the valid content files var allFiles = Directory.GetFiles(directory, "*" + ContentPaths.ContentFileSuffix, SearchOption.TopDirectoryOnly); // Filter out the files with invalid naming conventions, and sort them so we animate in the correct order var files = SortAndFilterFrameFiles(allFiles); // Build the individual frames var frames = new StationaryGrhData[files.Length]; for (var i = 0; i < frames.Length; i++) { var contentAssetName = ContentAssetName.FromAbsoluteFilePath(files[i], ContentPaths.Build.Grhs).Value; var textureAssetName = new TextureAssetName(contentAssetName); var frameGrhData = new StationaryGrhData(this, textureAssetName); frames[i] = frameGrhData; } if (frames.Length == 0) throw new Exception("Animation has no frames"); return frames; }
static string TryGetAbsoluteFilePath(StationaryGrhData gd, ContentPaths contentPath) { string ret = null; var isValid = true; try { ret = gd.TextureName.GetAbsoluteFilePath(contentPath); if (!File.Exists(ret)) isValid = false; } catch (ArgumentException) { isValid = false; } if (!isValid) { const string errmsg = "Could not update the size of GrhData `{0}` since the file for the texture named `{1}`" + " could not be found in the ContentPaths.Dev. Expected file: {2}"; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, gd, gd.TextureName, ret ?? "[NULL]"); return null; } else return ret; }
/// <summary> /// Initializes a new instance of the <see cref="ThreadPoolAsyncCallbackState"/> class. /// </summary> /// <param name="grhData">The <see cref="StationaryGrhData"/>.</param> /// <param name="callback">The callback to invoke when complete.</param> /// <param name="userState">The user state object.</param> /// <param name="wait">If true, performs a spin-wait. If false, generates the <see cref="Image"/> on the thread.</param> /// <param name="bitmap">The unscaled <see cref="Bitmap"/>. Only needed when <paramref name="wait"/> is false.</param> public ThreadPoolAsyncCallbackState(StationaryGrhData grhData, GrhImageListAsyncCallback callback, object userState, bool wait, Bitmap bitmap) { _grhData = grhData; _callback = callback; _userState = userState; _wait = wait; _bitmap = bitmap; }
/// <summary> /// Gets the image key for a <see cref="GrhData"/>. /// </summary> /// <param name="grhData">The <see cref="GrhData"/> to get the image key for.</param> /// <returns>The image key for the <paramref name="grhData"/>.</returns> protected virtual string GetImageKey(StationaryGrhData grhData) { if (grhData == null) return string.Empty; if (!grhData.GrhIndex.IsInvalid) { // For normal GrhDatas, we return the unique GrhIndex var grhIndex = grhData.GrhIndex; if (grhIndex.IsInvalid) return string.Empty; return grhIndex.ToString(); } else { // When we have a frame for a GrhData with an invalid GrhIndex, we prefix a "_" and the use the texture name var textureName = grhData.TextureName != null ? grhData.TextureName.ToString() : null; if (string.IsNullOrEmpty(textureName)) return string.Empty; else return "_" + textureName; } }
/// <summary> /// Asynchronously gets the <see cref="Image"/> for the given argument. /// </summary> /// <param name="gd">The <see cref="StationaryGrhData"/> to get the <see cref="Image"/> for.</param> /// <param name="callback">The <see cref="GrhImageListAsyncCallback"/> to invoke when the operation has finished.</param> /// <param name="userState">The optional user state object to pass to the <paramref name="callback"/>.</param> public void GetImageAsync(StationaryGrhData gd, GrhImageListAsyncCallback callback, object userState) { GetImage(gd, true, callback, userState); }
/// <summary> /// Gets the <see cref="Image"/> for the given argument. /// </summary> /// <param name="gd">The <see cref="StationaryGrhData"/> to get the <see cref="Image"/> for.</param> /// <param name="async">If true, asynchronous mode will be used. This will return null immediately if the desired /// <see cref="Image"/> has not yet been created.</param> /// <param name="callback">When <see cref="async"/> is false, contains the callback method to invoke when the <see cref="Image"/> /// has been created.</param> /// <param name="userState">The optional user state object to pass to the <paramref name="callback"/>.</param> /// <returns> /// The <see cref="Image"/> for the <paramref name="gd"/>, or null if <paramref name="async"/> is set. /// </returns> Image GetImage(StationaryGrhData gd, bool async, GrhImageListAsyncCallback callback, object userState) { if (gd == null) { if (!async) { // Return the ErrorImage directly return ErrorImage; } else { // Raise the callback and pass the ErrorImage if (callback != null) callback(this, gd, ErrorImage, userState); return null; } } // Get the key var key = GetImageKey(gd); // Get the image from the cache Image img; lock (_imagesSync) { // Check if the image already exists if (!_images.TryGetValue(key, out img)) { // Image does not exist, so add the placeholder since we are about to create it. Placing the placeholder // in there will make sure that no other threads try to create it at the same time. img = null; _images.Add(key, _placeholder); } } if (!async) { if (img != null) { if (img == _placeholder) { // If we got the placeholder image, do a spin-wait until we get the actual image, then return the image. This will // happen when another thread is creating the image. return SpinWaitForImage(key); } else { // Any other non-null image means that the image was already created, so we can just return it immediately return img; } } else { // Create it on this thread and return it when its done return CreateAndInsertImage(key, gd); } } else { if (img != null) { // When we get the placeholder image while in async mode, this is slightly more annoying since we have // to create another thread to spin-wait on if (img == _placeholder) { // Create the thread to spin-wait on... though only if we were given a callback method. Obviously does no // good to wait for the image when there is no callback method. if (callback != null) { var tpacs = new ThreadPoolAsyncCallbackState(gd, callback, userState, true, null); ExecuteOnThreadPool(tpacs); } } else { // But when we get the actual image, we can just invoke the callback directly from this thread. This is the // once scenario where no threads are created in async mode. if (callback != null) callback(this, gd, img, userState); } } else { // NOTE: The asynchronous aspect is less than optimal due to this. // When originally designing this, I was working under the assumption that SFML would be able to deal with the threading // better. Turns out I was wrong. There are probably some other threading issues that would have to be taken into // account, too, like that content can be disposed on the main thread. I could offload more onto the worker thread // than just the rescaling, such as the generation of the original unscaled bitmap, but the biggest gains come from // the ability to offload the actual image loading. I guess its helpful to have at least a little work offloaded // than to be completely synchronous since that does mean multi-core CPUs can load a bit faster. var bmp = CreateUnscaledBitmap(gd); if (bmp == null) { // If the bitmap failed to be created for whatever reason, use the ErrorImage if (callback != null) callback(this, gd, ErrorImage, userState); lock (_imagesSync) { Debug.Assert(_images[key] == _placeholder); _images[key] = ErrorImage; } } else { // Add the Image creation job to the thread pool var tpacs = new ThreadPoolAsyncCallbackState(gd, callback, userState, false, bmp); ExecuteOnThreadPool(tpacs); } } // Async always returns null return null; } }
/// <summary> /// Gets the tool tip text for a <see cref="StationaryGrhData"/>. /// </summary> /// <param name="grhData">The <see cref="StationaryGrhData"/>.</param> /// <returns>The tool tip text for a <see cref="StationaryGrhData"/>.</returns> static string GetToolTipTextStationary(StationaryGrhData grhData) { // Stationary var sb = new StringBuilder(); sb.AppendLine("Grh: " + grhData.GrhIndex); sb.AppendLine("Texture: " + grhData.TextureName); var sourceRect = grhData.SourceRect; sb.AppendLine("Pos: (" + sourceRect.X + "," + sourceRect.Y + ")"); sb.Append("Size: " + sourceRect.Width + "x" + sourceRect.Height); return sb.ToString(); }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copyArray = new StationaryGrhData[_frames.Length]; Array.Copy(_frames, copyArray, _frames.Length); var copy = new AnimatedGrhData(newGrhIndex, newCategorization) { _frames = copyArray }; copy.SetSpeed(Speed); return copy; }
void ShowGrhInfoForStationary(StationaryGrhData grhData) { // Stationary chkAutoSize.Checked = grhData.AutomaticSize; radioStationary.Checked = true; radioAnimated.Checked = false; var r = grhData.OriginalSourceRect; txtX.Text = r.X.ToString(); txtY.Text = r.Y.ToString(); txtW.Text = r.Width.ToString(); txtH.Text = r.Height.ToString(); txtTexture.ChangeTextToDefault(grhData.TextureName.ToString(), true); }
/// <summary> /// Sets the icon for a stationary <see cref="GrhData"/>. /// </summary> /// <param name="grhData">The <see cref="StationaryGrhData"/>.</param> void SetIconImageStationary(StationaryGrhData grhData) { _grhImageList.GetImageAsync(grhData, _asyncCallback, this); }
/// <summary> /// Callback for getting an <see cref="Image"/> asynchronously. /// We make this static and pass the <see cref="GrhTreeViewNode"/> as the <paramref name="userState"/> so we can /// just create a single delegate instance and pass that around, greatly reducing garbage and overhead of async operations. /// </summary> /// <param name="sender">The <see cref="GrhImageList"/> the callback came from.</param> /// <param name="gd">The <see cref="StationaryGrhData"/> that the <paramref name="image"/> is for. May be null if the /// <paramref name="image"/> is equal to <see cref="GrhImageList.ErrorImage"/> or null.</param> /// <param name="image">The <see cref="Image"/> that was created.</param> /// <param name="userState">The optional user state object that was passed to the method.</param> static void GrhImageListAsyncCallback(GrhImageList sender, StationaryGrhData gd, Image image, object userState) { var node = (GrhTreeViewNode)userState; var treeView = node.TreeView; if (treeView == null) return; // If the async callback was run on another thread, we will have to use Control.Invoke() to get it to the correct thread. // Unfortunately, this will result in a bit of overhead and create some garbage due to the parameter list, but // its the best we can do (as far as I can see) and GrhImageList avoids running a new thread when possible anyways so // it only really happens while loading. if (!treeView.InvokeRequired) SetNodeImage(node, image); else treeView.Invoke(_setNodeImage, node, image); }
/// <summary> /// Gets the <see cref="Image"/> for the given argument. /// </summary> /// <param name="gd">The <see cref="StationaryGrhData"/> to get the <see cref="Image"/> for.</param> /// <returns> /// The <see cref="Image"/> for the <paramref name="gd"/>. /// </returns> public Image GetImage(StationaryGrhData gd) { return GetImage(gd, false, null, null); }
/// <summary> /// Creates an <see cref="Image"/> for a <see cref="StationaryGrhData"/>, adds it to the cache, and returns it. /// Only call this if the image actually needs to be created, and never call this while in the <see cref="_imagesSync"/> /// lock! /// </summary> /// <param name="key">The key.</param> /// <param name="gd">The <see cref="StationaryGrhData"/>.</param> /// <returns>The <see cref="Image"/>.</returns> Image CreateAndInsertImage(string key, StationaryGrhData gd) { Image img; // The image was null, so we are going to be the ones to create it try { // Try to create the image var tex = gd.GetOriginalTexture(); if (tex == null) img = ErrorImage; else img = tex.ToBitmap(gd.OriginalSourceRect, ImageWidth, ImageHeight); } catch (Exception ex) { const string errmsg = "Failed to create GrhImageList image for `{0}`. Exception: {1}"; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, gd, ex); if (!(ex is LoadingFailedException)) Debug.Fail(string.Format(errmsg, gd, ex)); img = _errorImage; } if (log.IsDebugEnabled) log.DebugFormat("Created GrhImageList image for `{0}`.", img); // If we for some reason have the _placeholder or null image by this point, there is an error in the logic above. But to // avoid a deadlock (even though this should never happen), we will just set it to the ErrorImage if it somehow does. if (img == null || img == _placeholder) { const string errmsg = "Created image was either null or the placeholder image, which should never happen."; if (log.IsErrorEnabled) log.Error(errmsg); Debug.Fail(errmsg); img = _errorImage; } // Add the image to the cache (it will either be the correct image, or the ErrorImage if it failed). Remember that we // have already inserted the placeholder image at the key. So we're just replacing the placeholder with the actual image. lock (_imagesSync) { Debug.Assert(_images[key] == _placeholder); _images[key] = img; } // Return the generated image return img; }
StationaryGrhData[] CreateFrames(IList<GrhIndex> frameIndices) { var frames = new StationaryGrhData[frameIndices.Count]; for (var i = 0; i < frameIndices.Count; i++) { frames[i] = GrhInfo.GetData(frameIndices[i]) as StationaryGrhData; if (frames[i] == null) { const string errmsg = "Failed to load GrhData `{0}`. GrhData `{1}` needs it for frame index `{2}` (0-based), out of `{3}` frames total."; var err = string.Format(errmsg, frames[i], this, i, frameIndices.Count); throw new GrhDataException(this, err); } } return frames; }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copy = new StationaryGrhData(ContentManager, newGrhIndex, newCategorization, TextureName, AutomaticSize ? (Rectangle?)null : SourceRect); return copy; }
/// <summary> /// Creates an unscaled <see cref="Bitmap"/> of a <see cref="StationaryGrhData"/>. /// </summary> /// <param name="gd">The <see cref="StationaryGrhData"/>.</param> /// <returns>The unscaled <see cref="Bitmap"/>, or null if an error occured.</returns> static Bitmap CreateUnscaledBitmap(StationaryGrhData gd) { Bitmap img; // The image was null, so we are going to be the ones to create it try { // Try to create the image var tex = gd.GetOriginalTexture(); if (tex == null) img = null; else img = tex.ToBitmap(gd.OriginalSourceRect); } catch (Exception ex) { const string errmsg = "Failed to create GrhImageList image for `{0}`. Exception: {1}"; if (log.IsErrorEnabled) log.ErrorFormat(errmsg, gd, ex); if (!(ex is LoadingFailedException) && !(ex is OutOfMemoryException)) Debug.Fail(string.Format(errmsg, gd, ex)); img = null; } return img; }