xEvilReeperx

Members
  • Content count

    853
  • Joined

  • Last visited

Community Reputation

1053 Excellent

4 Followers

About xEvilReeperx

  • Rank
    Junior Rocket Scientist
  1. Odd, your example works fine on my system. Do you end up triggering any texture rebuilds while running yours? Are you testing with the pre-release or the current version? Win7 GTX 960 Current version (not localization pre-release) Installed fonts according to Font.GetOSInstalledFontNames:
  2. That mod only. Yes I knew it's pretty ugly code, it was just slapped together to check things out
  3. @Steven Mading 3 and 4 are for rendering done in OnGUI. Those lines are for 1 and 2 to match the order of initialization you specified in the OP
  4. Something sounds fishy here. Are you editing the default GUI.skin? Why? I couldn't reproduce the problem with the following: abstract class TestTextRenderer { private Rect _rect = new Rect(0f, 0f, 300f, 300f); private GUISkin _skin; private string _title; private string _text; public virtual void Initialize() { _skin = GetSkin(); _title = GetTitle(); _rect.center = new Vector2(Screen.width * 0.5f, Screen.height * 0.5f); _text = Enumerable.Range(0, 255).Select(idx => ((char)idx).ToString()).Aggregate((s1, s2) => s1 + s2); _text += _text += _text; // increase length a bit Log.Normal(_title + " font: " + _skin.font.name + ": " + string.Join(",", _skin.font.fontNames)); } protected abstract GUISkin GetSkin(); protected abstract string GetTitle(); public void DrawWindow() { GUI.skin = _skin; _rect = KSPUtil.ClampRectToScreen(GUILayout.Window(_title.GetHashCode(), _rect, DoWindow, _title)); } private void DoWindow(int winid) { GUILayout.TextArea(_text); GUI.DragWindow(); } } class DynamicTextRenderer : TestTextRenderer { private const string FontName = "Arial Bold"; private const int FontSize = 16; protected override GUISkin GetSkin() { var cloneSkin = UnityEngine.Object.Instantiate(HighLogic.Skin); var dynFont = Font.CreateDynamicFontFromOSFont(FontName, FontSize); cloneSkin.font = dynFont; return cloneSkin; } protected override string GetTitle() { return "Dynamic Text"; } } class ResourceTextRenderer : TestTextRenderer { private readonly Font[] _resourceFonts; private const string FontName = "Arial"; public ResourceTextRenderer([NotNull] Font[] resourceFonts) { if (resourceFonts == null) throw new ArgumentNullException("resourceFonts"); _resourceFonts = resourceFonts; } protected override GUISkin GetSkin() { var cloneSkin = UnityEngine.Object.Instantiate(HighLogic.Skin); cloneSkin.font = _resourceFonts.First(f => f.fontNames.Contains(FontName)); return cloneSkin; } protected override string GetTitle() { return "Resource-loaded Font"; } } // controls ordering of text initialization and rendering // 1. init resource font // 2. init dyn font // 3. make sure dyn font renders first // 4. res font renders second [KSPAddon(KSPAddon.Startup.MainMenu, true)] public class RenderOrderController : MonoBehaviour { private DynamicTextRenderer _dynText = null; private ResourceTextRenderer _resText = null; private void Awake() { Font.textureRebuilt += FontOnTextureRebuilt; _resText = new ResourceTextRenderer(Resources.FindObjectsOfTypeAll<Font>()); _resText.Initialize(); _dynText = new DynamicTextRenderer(); _dynText.Initialize(); } private void OnDestroy() { Font.textureRebuilt -= FontOnTextureRebuilt; } private void FontOnTextureRebuilt(Font font) { Log.Warning("Font rebuilt: {0}; {1}", font.name, string.Join(",", font.fontNames)); } private void OnGUI() { var origSkin = GUI.skin; _dynText.DrawWindow(); GUI.skin = origSkin; _resText.DrawWindow(); } }
  5. You could get the instance IDs of textures, too. This would fail if they update their image by changing UVs. But why copy the texture? You can just grab a reference to it and use it directly. I thought you were only interested in the buttons themselves, which contain everything you need to render a duplicate version (if I understand what you're attempting to do correctly) by themselves. That's not the expensive call. If the texture is readable, a copy of it exists in system memory so Unity won't stall the GPU to read pixels back from it. It's ReadPixels that's terribly slow (if you haven't got any unreadable textures, this isn't hurting you so far). The problem with that one is simply that every time you call it, an array allocation will be made. Why do you need a clone of the texture? Are you modifying it later?
  6. Every UnityEngine.Object has a unique identifier you can get with GetInstanceID(). One simple fix is to look at the instance IDs of the buttons themselves. If you want max efficiency, you could eliminate the constant rechecking and comparisons entirely by setting up a little tracking MonoBehaviour that sets off an event whenever a button is created or destroyed (or whatever else you'd like) public static class ToolbarEvents { public enum ChangeType { Added, Removed } // just like GameEvents public static readonly EventData<ApplicationLauncherButton, ChangeType> ButtonChange = new EventData<ApplicationLauncherButton, ChangeType>("AppLauncher_ButtonEvent"); [KSPAddon(KSPAddon.Startup.MainMenu, true)] private class Install : MonoBehaviour { private IEnumerator Start() { while (ApplicationLauncher.Instance == null) yield return null; var al = ApplicationLauncher.Instance; ButtonEventDispatcher.AddToPrefab(al.listItemPrefab); foreach (var b in al.GetComponentsInChildren<ApplicationLauncherButton>(true)) ButtonChange.Fire(b, ChangeType.Added); Destroy(gameObject); } } private class ButtonEventDispatcher : MonoBehaviour { [SerializeField] private ApplicationLauncherButton _ourButton; // Unity instantiation serialization will make sure this points // to the live button when created private static ApplicationLauncherButton _buttonPrefab; // this used to prevent Unity from unloading prefab from memory // should it stop being referenced at some point public static void AddToPrefab(ApplicationLauncherButton prefab) { var ourPrefab = prefab.gameObject.AddComponent<ButtonEventDispatcher>(); ourPrefab._ourButton = prefab; _buttonPrefab = prefab; } private void Start() { ButtonChange.Fire(_ourButton, ChangeType.Added); } private void OnDestroy() { ButtonChange.Fire(_ourButton, ChangeType.Removed); } // can also do events for hiding/showing if desired } }
  7. This is the worst one. GetPixels32() as well. This code will also potentially be very costly to run, potentially framerate-killing, because it'll stall the graphics pipeline every time you ReadPixels. I assume the error catch here is a fallback for unreadable textures As a final punch in the pants, it'll quickly consume all available heap mem because you never destroy the Texture2D. Eventually Unity will force a cleanup and the unreferenced ones will be destroyed. What is the purpose of this code? You seem to know that you can just read a texture directly from one of the components used in ApplicationLauncher buttons. If you want to copy the texture to manipulate it, instantiate a copy instead. I don't see where it's ultimately manipulated on a first glance though
  8. Flood gates have been open for literal years. I personally never received a complaint about ScienceAlert's animated button before. Ultimately it's up to the modder @linuxgurugamerThere's an overload of the AddModApplication method that accepts an Animator. Set your button up inside Unity (Animator + Sprite Renderer, scale 100,100,100, layer "UI", renderer sorting layer "Apps", frame size 38x38). Create a prefab out of it, load it inside KSP via AssetBundle, instantiate an instance and pass it as an argument. If you need more detail, let me know and I'll put together a complete example
  9. I've seen the NullRef in ReplaceTextures before when the main texture on the flag transform renderer material wasn't set to anything (ex: mk1pod). Any unset material texture on any renderer in the part would theoretically [haven't tested it, but logically] cause that method to throw if you attempted to replace any of the part's textures, unwinding all the way back to PartLoader and causing the part to fail to compile. If you have a zip of this particular part somewhere, it would be helpful to track down whatever the problem is if it isn't the unset texture thing
  10. I question the use of a hash at all in this case. Putting this information in a header at the start of the sfs would've been a lot better. When is the sfs hash going to be different than the saved one anyway, barring user intervention or corruption? There's no need to read the entirety of every sfs into memory every time MainMenu opens
  11. Close if you would please, @linuxgurugamer is continuing this for me with a new thread
  12. If all requesters are sharing the same reference, why provide a unique identifier at all? If you want AGX to call a method and pass a list/dictionary/whatever reference, why not do that directly instead of dealing with two separate GameEvents? Here's how I'd envision that version:
  13. You shouldn't try to link the life cycle of a GameEvent-type class with anything but static because it won't be cleaned up anyway. They register themselves in a static, private dictionary by name. The only time a previously created EventData will be GC is if a new, identically-named one is created and no other references to the previous one exist. For example, this event will exist for the lifetime of KSP, long beyond its creator, because EventData<T> registers itself in a static dictionary inside BaseGameEvent: [KSPAddon(KSPAddon.Startup.MainMenu, true)] class HelloWorld : MonoBehaviour { private void Awake() { var testTheEvent = new EventData<string>("helloworld"); } } Now, subscribers could be prevented from GC cleanup of course but since the only subscriber to my example event is the event's owner, controlling its lifetime is much simpler
  14. Well, if you go with the one GameEvent, life cycle and all that shouldn't be a concern because the only thing that listens for the event is the event's owner anyway. Maybe it'll make more sense with a code example. Keep in mind that whatever parameter you decide to pass will need to be a reference type like @hvacengisaid [KSPAddon(KSPAddon.Startup.MainMenu, false)] class ModB : MonoBehaviour { private EventData<Dictionary<int, bool>> _actionGroupStateQuery; private void Awake() { _actionGroupStateQuery = GameEvents.FindEvent<EventData<Dictionary<int, bool>>>("AGX_QueryActionGroupStates"); var currentStates = new Dictionary<int, bool>(); _actionGroupStateQuery.Fire(currentStates); foreach (var state in currentStates) Debug.Log("Action group: " + state.Key + ": " + state.Value); } } class AGX { // note how AGX is the only thing that listens to this event. Mods who want to know stuff will fire it instead of listening public static readonly EventData<Dictionary<int, bool>> QueryActionGroupStates = new EventData<Dictionary<int, bool>>("AGX_QueryActionGroupStates"); // get around static method limitation of GameEvents private class Forwarder { private readonly EventData<Dictionary<int, bool>>.OnEvent _del; public Forwarder(EventData<Dictionary<int, bool>>.OnEvent del) { _del = del; } public void OnEvent(Dictionary<int, bool> data) { _del(data); } } // make sure initialization happens early [KSPAddon(KSPAddon.Startup.Instantly, true)] private class Initialize : MonoBehaviour { private void Awake() { Init(); Destroy(gameObject); } } private static void Init() { var forwarder = new Forwarder(OnQueryReceived); QueryActionGroupStates.Add(forwarder.OnEvent); } // somebody wants to know some stuff private static void OnQueryReceived(Dictionary<int, bool> data) { for (int i = 0; i < 32; ++i) data[i] = i % 2 == 0; } }
  15. Why not spin it around and make AGX the thing that listens for the event? Have the requester fire the event instead, AGX can fill in the desired info. You'd only need one event this way as well