From 97c3ac141251316589d2514f13c712871605e462 Mon Sep 17 00:00:00 2001 From: Regalis Date: Sun, 27 Sep 2015 17:02:15 +0300 Subject: [PATCH] RegEx, FindPickable & DrawShadows optimization --- Subsurface/Source/Characters/Character.cs | 130 ++++++++++-------- .../Components/Signal/RegExFindComponent.cs | 51 +++++-- Subsurface/Source/Items/Item.cs | 8 +- Subsurface/Source/Map/Lights/ConvexHull.cs | 5 +- Subsurface/Source/Map/Lights/LightManager.cs | 10 +- Subsurface/Source/Screens/EditMapScreen.cs | 8 +- Subsurface_Solution.sln | 3 + Subsurface_Solution.v12.suo | Bin 702464 -> 705536 bytes 8 files changed, 124 insertions(+), 91 deletions(-) diff --git a/Subsurface/Source/Characters/Character.cs b/Subsurface/Source/Characters/Character.cs index a498b37f1..969af89cf 100644 --- a/Subsurface/Source/Characters/Character.cs +++ b/Subsurface/Source/Characters/Character.cs @@ -487,7 +487,9 @@ namespace Subsurface return Info.Job.GetSkillLevel(skillName); } - public void Control(float deltaTime, Camera cam, bool forcePick = false) + float findClosestTimer; + + public void Control(float deltaTime, Camera cam) { if (isDead) return; @@ -530,63 +532,6 @@ namespace Subsurface } } - //find the closest item if selectkey has been hit, or if the character is being - //controlled by the player (in order to highlight it) - if (controlled == this) - { - Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition)); - - closestCharacter = FindClosestCharacter(mouseSimPos); - if (closestCharacter != null) - { - if (closestCharacter != selectedCharacter) selectedCharacter = null; - if (!closestCharacter.isHumanoid) closestCharacter = null; - } - - - closestItem = FindClosestItem(mouseSimPos); - - if (closestCharacter != null && closestItem != null) - { - if (Vector2.Distance(closestCharacter.SimPosition, mouseSimPos) < Vector2.Distance(closestItem.SimPosition, mouseSimPos)) - { - if (selectedConstruction!=closestItem) closestItem = null; - } - else - { - closestCharacter = null; - } - } - - if (selectedCharacter==null) - { - if (closestItem != null) - { - closestItem.IsHighlighted = true; - if (GetInputState(InputType.Select) && closestItem.Pick(this, forcePick)) - { - new NetworkEvent(NetworkEventType.PickItem, ID, true, closestItem.ID); - } - } - } - else - { - if (Vector2.Distance(selectedCharacter.SimPosition, SimPosition) > 2.0f) selectedCharacter = null; - } - - if (GetInputState(InputType.Select)) - { - if (selectedCharacter!=null) - { - selectedCharacter = null; - } - else if (closestCharacter!=null && closestCharacter.isDead && closestCharacter.isHumanoid) - { - selectedCharacter = closestCharacter; - } - } - } - for (int i = 0; i < selectedItems.Length; i++ ) { if (selectedItems[i] == null) continue; @@ -616,7 +561,7 @@ namespace Subsurface Limb torso = AnimController.GetLimb(LimbType.Torso); Vector2 pos = (torso.body.TargetPosition != Vector2.Zero) ? torso.body.TargetPosition : torso.SimPosition; - return Item.FindPickable(pos, selectedConstruction == null ? mouseSimPos : selectedConstruction.SimPosition, null, selectedItems); + return Item.FindPickable(pos, selectedConstruction == null ? mouseSimPos : selectedConstruction.SimPosition, AnimController.CurrentHull, selectedItems); } private Character FindClosestCharacter(Vector2 mouseSimPos, float maxDist = 150.0f) @@ -647,7 +592,7 @@ namespace Subsurface /// /// Control the character according to player input /// - public void ControlLocalPlayer(Camera cam, bool moveCam = true) + public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true) { //if (isDead) //{ @@ -704,6 +649,69 @@ namespace Subsurface } } + + //find the closest item if selectkey has been hit, or if the character is being + //controlled by the player (in order to highlight it) + + if (findClosestTimer <= 0.0f || Screen.Selected == GameMain.EditMapScreen) + { + closestCharacter = FindClosestCharacter(mouseSimPos); + if (closestCharacter != null) + { + if (closestCharacter != selectedCharacter) selectedCharacter = null; + if (!closestCharacter.isHumanoid) closestCharacter = null; + } + + closestItem = FindClosestItem(mouseSimPos); + + if (closestCharacter != null && closestItem != null) + { + if (Vector2.Distance(closestCharacter.SimPosition, mouseSimPos) < Vector2.Distance(closestItem.SimPosition, mouseSimPos)) + { + if (selectedConstruction != closestItem) closestItem = null; + } + else + { + closestCharacter = null; + } + } + + findClosestTimer = 0.1f; + } + else + { + findClosestTimer -= deltaTime; + } + + if (selectedCharacter == null) + { + if (closestItem != null) + { + closestItem.IsHighlighted = true; + if (GetInputState(InputType.Select) && closestItem.Pick(this, false)) + { + new NetworkEvent(NetworkEventType.PickItem, ID, true, closestItem.ID); + } + } + } + else + { + if (Vector2.Distance(selectedCharacter.SimPosition, SimPosition) > 2.0f) selectedCharacter = null; + } + + if (GetInputState(InputType.Select)) + { + if (selectedCharacter != null) + { + selectedCharacter = null; + } + else if (closestCharacter != null && closestCharacter.isDead && closestCharacter.isHumanoid) + { + selectedCharacter = closestCharacter; + } + } + + DisableControls = false; } @@ -744,7 +752,7 @@ namespace Subsurface if (controlled == this) { CharacterHUD.Update(deltaTime,this); - ControlLocalPlayer(cam); + ControlLocalPlayer(deltaTime, cam); } Control(deltaTime, cam); diff --git a/Subsurface/Source/Items/Components/Signal/RegExFindComponent.cs b/Subsurface/Source/Items/Components/Signal/RegExFindComponent.cs index 917c45ac8..36d2e5353 100644 --- a/Subsurface/Source/Items/Components/Signal/RegExFindComponent.cs +++ b/Subsurface/Source/Items/Components/Signal/RegExFindComponent.cs @@ -10,6 +10,11 @@ namespace Subsurface.Items.Components private string expression; private string receivedSignal; + private string previousReceivedSignal; + + bool previousResult; + + private Regex regex; [InGameEditable, HasDefaultValue("1", true)] public string Output @@ -22,7 +27,22 @@ namespace Subsurface.Items.Components public string Expression { get { return expression; } - set { expression = value; } + set + { + if (expression == value) return; + expression = value; + + try + { + regex = new Regex(@expression); + } + + catch + { + item.SendSignal("ERROR", "signal_out"); + return; + } + } } public RegExFindComponent(Item item, XElement element) @@ -33,22 +53,26 @@ namespace Subsurface.Items.Components public override void Update(float deltaTime, Camera cam) { - if (string.IsNullOrWhiteSpace(expression)) return; + if (string.IsNullOrWhiteSpace(expression) || regex==null) return; - bool success = false; - try + if (receivedSignal!=previousReceivedSignal) { - Regex regex = new Regex(@expression); - Match match = regex.Match(receivedSignal); - success = match.Success; - } - catch - { - item.SendSignal("ERROR", "signal_out"); - return; + try + { + Match match = regex.Match(receivedSignal); + previousResult = match.Success; + + } + catch + { + item.SendSignal("ERROR", "signal_out"); + previousResult = false; + return; + } } - item.SendSignal(success ? output : "0", "signal_out"); + + item.SendSignal(previousResult ? output : "0", "signal_out"); } public override void ReceiveSignal(string signal, Connection connection, Item sender, float power = 0.0f) @@ -57,7 +81,6 @@ namespace Subsurface.Items.Components { case "signal_in": receivedSignal = signal; - break; case "set_output": output = signal; diff --git a/Subsurface/Source/Items/Item.cs b/Subsurface/Source/Items/Item.cs index 5b0dd2027..4481045ba 100644 --- a/Subsurface/Source/Items/Item.cs +++ b/Subsurface/Source/Items/Item.cs @@ -326,11 +326,7 @@ namespace Subsurface public void SetTransform(Vector2 simPosition, float rotation) { - if (body != null) - { - body.SetTransform(simPosition, rotation); - } - + if (body != null) body.SetTransform(simPosition, rotation); Vector2 displayPos = ConvertUnits.ToDisplayUnits(simPosition); @@ -770,7 +766,7 @@ namespace Subsurface foreach (Item item in itemList) { if (ignoredItems!=null && ignoredItems.Contains(item)) continue; - if (hull != null && item.CurrentHull != hull) continue; + if (hull != item.CurrentHull) continue; if (item.body != null && !item.body.Enabled) continue; Pickable pickableComponent = item.GetComponent(); diff --git a/Subsurface/Source/Map/Lights/ConvexHull.cs b/Subsurface/Source/Map/Lights/ConvexHull.cs index 4c99cbb60..e2f115ea8 100644 --- a/Subsurface/Source/Map/Lights/ConvexHull.cs +++ b/Subsurface/Source/Map/Lights/ConvexHull.cs @@ -111,7 +111,7 @@ namespace Subsurface.Lights } } - public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Vector2 lightSourcePos, bool los = true) + public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Vector2 lightSourcePos, Matrix transform, bool los = true) { if (!Enabled) return; @@ -224,8 +224,7 @@ namespace Subsurface.Lights currentIndex = (currentIndex + 1) % primitiveCount; } - shadowEffect.World = cam.ShaderTransform - * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; + shadowEffect.World = transform; shadowEffect.CurrentTechnique.Passes[0].Apply(); graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, shadowVertices, 0, shadowVertexCount * 2 - 2); diff --git a/Subsurface/Source/Map/Lights/LightManager.cs b/Subsurface/Source/Map/Lights/LightManager.cs index 3d0565ad9..2f53ce7f5 100644 --- a/Subsurface/Source/Map/Lights/LightManager.cs +++ b/Subsurface/Source/Map/Lights/LightManager.cs @@ -59,18 +59,24 @@ namespace Subsurface.Lights { Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height); + Matrix shadowTransform = cam.ShaderTransform + * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; + if (!LosEnabled) return; foreach (ConvexHull convexHull in ConvexHull.list) { if (!camView.Intersects(convexHull.BoundingBox)) continue; - convexHull.DrawShadows(graphics, cam, pos); + convexHull.DrawShadows(graphics, cam, pos, shadowTransform); } } public void DrawLightmap(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam) { + Matrix shadowTransform = cam.ShaderTransform + * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; + graphics.SetRenderTarget(lightMap); Rectangle viewRect = cam.WorldView; @@ -96,7 +102,7 @@ namespace Subsurface.Lights { if (!MathUtils.CircleIntersectsRectangle(light.Position, light.Range, ch.BoundingBox)) continue; //draw shadow - ch.DrawShadows(graphics, cam, light.Position, false); + ch.DrawShadows(graphics, cam, light.Position, shadowTransform, false); } //draw the light shape diff --git a/Subsurface/Source/Screens/EditMapScreen.cs b/Subsurface/Source/Screens/EditMapScreen.cs index 8e1f8f259..dedcc3737 100644 --- a/Subsurface/Source/Screens/EditMapScreen.cs +++ b/Subsurface/Source/Screens/EditMapScreen.cs @@ -225,7 +225,7 @@ namespace Subsurface } } - dummyCharacter.ControlLocalPlayer(cam, false); + dummyCharacter.ControlLocalPlayer((float)deltaTime, cam); dummyCharacter.Control((float)deltaTime, cam); } else @@ -287,6 +287,8 @@ namespace Subsurface { if (dummyCharacter != null) { + dummyCharacter.AnimController.FindHull(); + foreach (Item item in dummyCharacter.SelectedItems) { if (item == null) continue; @@ -311,10 +313,6 @@ namespace Subsurface if (PlayerInput.GetMouseState.LeftButton != ButtonState.Pressed) { - //if (Inventory.draggingItem!=null) - //{ - // Inventory.draggingItem.see - //} Inventory.draggingItem = null; } } diff --git a/Subsurface_Solution.sln b/Subsurface_Solution.sln index 895b06759..786e0b01e 100644 --- a/Subsurface_Solution.sln +++ b/Subsurface_Solution.sln @@ -396,4 +396,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/Subsurface_Solution.v12.suo b/Subsurface_Solution.v12.suo index 40c75b0e847238de90529c97b48e5ff6bffa2a2d..72d4d8b016910d7715e59db6581fd5e92d18480a 100644 GIT binary patch delta 9031 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 $n{5E4VYgs_r8Ti=KoogbEh#$ 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