Merge pull request #19 from Clancey/newHotReload

New hot reload
This commit is contained in:
James Clancey 2019-07-07 14:39:02 -08:00 коммит произвёл GitHub
Родитель 4c2214adf3 8a2e0bb1d4
Коммит 611dc5b092
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 196 добавлений и 12 удалений

Просмотреть файл

@ -39,14 +39,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{586C3CCB
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotUI.Tests", "tests\HotUI.Tests\HotUI.Tests.csproj", "{EA098E78-E56C-43B2-9D78-D4565EC04D09}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotUI.Tests", "tests\HotUI.Tests\HotUI.Tests.csproj", "{EA098E78-E56C-43B2-9D78-D4565EC04D09}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.WPF", "src\HotUI.WPF\HotUI.WPF.csproj", "{F8BA5DE0-AEC0-4423-B036-5F9157E939D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.UWP.Sample", "sample\HotUI.UWP.Sample\HotUI.UWP.Sample.csproj", "{35664234-EB75-47BA-A082-9F96BDF6FC92}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.UWP.Sample", "sample\HotUI.UWP.Sample\HotUI.UWP.Sample.csproj", "{35664234-EB75-47BA-A082-9F96BDF6FC92}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.WPF.Sample", "sample\HotUI.WPF.Sample\HotUI.WPF.Sample.csproj", "{0FC87DE5-B5E5-4846-894B-824497118143}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.WPF.Sample", "sample\HotUI.WPF.Sample\HotUI.WPF.Sample.csproj", "{0FC87DE5-B5E5-4846-894B-824497118143}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.UWP", "src\HotUI.UWP\HotUI.UWP.csproj", "{2D337300-B3E9-4A36-8387-76A9A1100C5B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.UWP", "src\HotUI.UWP\HotUI.UWP.csproj", "{2D337300-B3E9-4A36-8387-76A9A1100C5B}"
EndProject EndProject
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "HotUI.WPF", "src\HotUI.WPF\HotUI.WPF.csproj", "{A8D46A59-EFE4-434B-87EE-FC9085CF559A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -271,6 +271,18 @@ Global
{2D337300-B3E9-4A36-8387-76A9A1100C5B}.Release|iPhone.Build.0 = Release|Any CPU {2D337300-B3E9-4A36-8387-76A9A1100C5B}.Release|iPhone.Build.0 = Release|Any CPU
{2D337300-B3E9-4A36-8387-76A9A1100C5B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {2D337300-B3E9-4A36-8387-76A9A1100C5B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{2D337300-B3E9-4A36-8387-76A9A1100C5B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {2D337300-B3E9-4A36-8387-76A9A1100C5B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Debug|iPhone.Build.0 = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Release|Any CPU.Build.0 = Debug|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Release|iPhone.ActiveCfg = Release|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Release|iPhone.Build.0 = Release|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{A8D46A59-EFE4-434B-87EE-FC9085CF559A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -290,10 +302,10 @@ Global
{BCAF5569-30DB-4D44-BF46-DFFE93DDCBD0} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4} {BCAF5569-30DB-4D44-BF46-DFFE93DDCBD0} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
{1E8FBBC2-9E06-4960-A116-65CDEFEFE11D} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4} {1E8FBBC2-9E06-4960-A116-65CDEFEFE11D} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
{EA098E78-E56C-43B2-9D78-D4565EC04D09} = {586C3CCB-82A5-47F0-A099-B9A31BB4EA88} {EA098E78-E56C-43B2-9D78-D4565EC04D09} = {586C3CCB-82A5-47F0-A099-B9A31BB4EA88}
{F8BA5DE0-AEC0-4423-B036-5F9157E939D0} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
{35664234-EB75-47BA-A082-9F96BDF6FC92} = {C3FDCDB7-0C29-472D-A406-E2F21A4B0EAE} {35664234-EB75-47BA-A082-9F96BDF6FC92} = {C3FDCDB7-0C29-472D-A406-E2F21A4B0EAE}
{0FC87DE5-B5E5-4846-894B-824497118143} = {C3FDCDB7-0C29-472D-A406-E2F21A4B0EAE} {0FC87DE5-B5E5-4846-894B-824497118143} = {C3FDCDB7-0C29-472D-A406-E2F21A4B0EAE}
{2D337300-B3E9-4A36-8387-76A9A1100C5B} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4} {2D337300-B3E9-4A36-8387-76A9A1100C5B} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
{A8D46A59-EFE4-434B-87EE-FC9085CF559A} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0653DB4A-5BBE-4D78-99B2-DB1C82663246} SolutionGuid = {0653DB4A-5BBE-4D78-99B2-DB1C82663246}

Просмотреть файл

@ -17,7 +17,10 @@ namespace HotUI.Android.Sample
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
{ {
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
Page = new MainPage(); //#if DEBUG
// HotUI.Reload.Init ();
//#endif
Page = new MainPage();
} }
} }
} }

Просмотреть файл

@ -23,7 +23,10 @@ namespace HotUI.Mac.Sample
_window.ContentViewController = new MainPage().ToViewController(); _window.ContentViewController = new MainPage().ToViewController();
_window.IsVisible = true; _window.IsVisible = true;
_window.MakeKeyAndOrderFront(this);*/ _window.MakeKeyAndOrderFront(this);*/
} //#if DEBUG
// HotUI.Reload.Init ();
//#endif
}
public override void WillTerminate(NSNotification notification) public override void WillTerminate(NSNotification notification)
{ {

Просмотреть файл

@ -22,7 +22,9 @@ namespace HotUI.iOS.Sample {
{ {
// Override point for customization after application launch. // Override point for customization after application launch.
// If not required for your application you can safely delete this method // If not required for your application you can safely delete this method
//#if DEBUG
// HotUI.Reload.Init ();
//#endif
window = new UIWindow { window = new UIWindow {
RootViewController = new MainPage ().ToViewController (), RootViewController = new MainPage ().ToViewController (),
}; };

Просмотреть файл

@ -26,6 +26,7 @@
<MtouchLink>None</MtouchLink> <MtouchLink>None</MtouchLink>
<MtouchDebug>true</MtouchDebug> <MtouchDebug>true</MtouchDebug>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<MtouchExtraArgs>--enable-repl</MtouchExtraArgs>
<MtouchProfiling>true</MtouchProfiling> <MtouchProfiling>true</MtouchProfiling>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
@ -51,6 +52,7 @@
<CodesignKey>iPhone Developer</CodesignKey> <CodesignKey>iPhone Developer</CodesignKey>
<MtouchDebug>true</MtouchDebug> <MtouchDebug>true</MtouchDebug>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<MtouchExtraArgs>--interpreter</MtouchExtraArgs>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>

Просмотреть файл

@ -26,7 +26,7 @@ namespace HotUI.iOS
{ {
var cell = DequeueReusableCell(CellType) as ViewCell ?? new ViewCell(); var cell = DequeueReusableCell(CellType) as ViewCell ?? new ViewCell();
var item = _listView?.List[indexPath.Row]; var item = _listView?.List[indexPath.Row];
var v = _listView?.CellCreator(item); var v = _listView?.CellCreator?.Invoke(item);
v.Parent = _listView; v.Parent = _listView;
cell.SetView(v); cell.SetView(v);
return cell; return cell;
@ -38,7 +38,7 @@ namespace HotUI.iOS
{ {
_listView = view as ListView; _listView = view as ListView;
//TODO: Some crude size estimation //TODO: Some crude size estimation
var v = _listView.CellCreator(_listView.List[0]); var v = _listView?.CellCreator?.Invoke(_listView?.List[0]);
EstimatedRowHeight = 200; EstimatedRowHeight = 200;
ReloadData(); ReloadData();
} }

Просмотреть файл

@ -4,7 +4,7 @@ namespace HotUI {
public void Navigate (View view) public void Navigate (View view)
{ {
view.Navigation = this; view.Navigation = this;
view.Parent = this; view.UpdateNavigation ();
PerformNavigate (view); PerformNavigate (view);
} }
public Action<View> PerformNavigate; public Action<View> PerformNavigate;

Просмотреть файл

@ -28,6 +28,10 @@ namespace HotUI {
OnParentChange (value); OnParentChange (value);
} }
} }
internal void UpdateNavigation()
{
OnParentChange (Navigation);
}
protected virtual void OnParentChange(View parent) protected virtual void OnParentChange(View parent)
{ {
this.Navigation = parent.Navigation ?? parent as NavigationView; this.Navigation = parent.Navigation ?? parent as NavigationView;
@ -37,6 +41,7 @@ namespace HotUI {
public View (bool hasConstructors) public View (bool hasConstructors)
{ {
ActiveViews.Add (this); ActiveViews.Add (this);
HotReloadHelper.Register (this);
Context.View = this; Context.View = this;
State = StateBuilder.CurrentState ?? new State { State = StateBuilder.CurrentState ?? new State {
StateChanged = ResetView StateChanged = ResetView
@ -62,6 +67,8 @@ namespace HotUI {
return; return;
viewHandler?.Remove (this); viewHandler?.Remove (this);
viewHandler = value; viewHandler = value;
if (replacedView != null)
replacedView.ViewHandler = value;
WillUpdateView (); WillUpdateView ();
viewHandler?.SetView (this.GetRenderView()); viewHandler?.SetView (this.GetRenderView());
} }
@ -78,17 +85,20 @@ namespace HotUI {
} }
View builtView; View builtView;
public View BuiltView => builtView; public View BuiltView => builtView;
internal void Reload () => ResetView ();
void ResetView() void ResetView()
{ {
if (usedEnvironmentData.Any ()) if (usedEnvironmentData.Any ())
SetEnvironmentFields (); SetEnvironmentFields ();
var oldView = builtView; var oldView = builtView;
builtView = null; builtView = null;
replacedView?.Dispose ();
replacedView = null;
if (ViewHandler == null) if (ViewHandler == null)
return; return;
ViewHandler.Remove (this); ViewHandler.Remove (this);
var view = this.GetRenderView ().Diff (oldView); var view = this.GetRenderView ().Diff (oldView);
oldView.Dispose (); oldView?.Dispose ();
ViewHandler?.SetView (view); ViewHandler?.SetView (view);
} }
@ -100,9 +110,19 @@ namespace HotUI {
} }
internal View GetView () => GetRenderView (); internal View GetView () => GetRenderView ();
View replacedView;
protected virtual View GetRenderView () protected virtual View GetRenderView ()
{ {
if (replacedView != null)
return replacedView.GetRenderView();
var replaced = HotReloadHelper.GetReplacedView (this);
if(replaced != this) {
replaced.Navigation = this.Navigation;
replaced.Parent = this.Parent;
replacedView = replaced;
replacedView.ViewHandler = ViewHandler;
return replacedView.GetRenderView();
}
if (Body == null) if (Body == null)
return this; return this;
if (builtView != null) if (builtView != null)
@ -130,6 +150,7 @@ namespace HotUI {
{ {
this.SetPropertyValue (property, value); this.SetPropertyValue (property, value);
ViewHandler?.UpdateValue (property, value); ViewHandler?.UpdateValue (property, value);
replacedView?.ViewPropertyChanged (property, value);
} }
internal virtual void ContextPropertyChanged(string property, object value) internal virtual void ContextPropertyChanged(string property, object value)
@ -181,10 +202,11 @@ namespace HotUI {
public void Dispose () public void Dispose ()
{ {
ActiveViews.Remove (this); ActiveViews.Remove (this);
HotReloadHelper.UnRegister (this);
ViewHandler = null; ViewHandler = null;
Body = null; Body = null;
Context.Clear (); Context.Clear ();
State.DisposingObject(this); State?.DisposingObject(this);
State = null; State = null;
OnDisposing (); OnDisposing ();
} }

Просмотреть файл

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using HotUI.Services; using HotUI.Services;
[assembly: InternalsVisibleTo ("HotUI.Tests")] [assembly: InternalsVisibleTo ("HotUI.Tests")]
[assembly: InternalsVisibleTo ("HotUI.Reload.Tests")]
namespace HotUI{ namespace HotUI{
public static class Device { public static class Device {

Просмотреть файл

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace HotUI {
public static class HotReloadHelper {
static HotReloadHelper()
{
IsEnabled = Debugger.IsAttached;
}
public static bool IsEnabled { get; set; }
public static void Register(View view, params object[] parameters)
{
if (!IsEnabled)
return;
currentViews [view] = parameters;
}
public static void UnRegister(View view)
{
if (!IsEnabled)
return;
currentViews.Remove (view);
}
public static View GetReplacedView(View view)
{
if (!IsEnabled)
return view;
if (!replacedViews.TryGetValue (view.GetType ().FullName, out var newViewType))
return view;
currentViews.TryGetValue (view, out var parameters);
var newView = parameters?.Length > 0 ? Activator.CreateInstance (newViewType, args: parameters) : Activator.CreateInstance (newViewType);
//TODO: Apply state!
return (View)newView;
}
static Dictionary<string, Type> replacedViews = new Dictionary<string, Type> ();
static Dictionary<View, object []> currentViews = new Dictionary<View, object []> ();
public static void RegisterReplacedView(string oldViewType, Type newViewType)
{
if (!IsEnabled)
return;
replacedViews [oldViewType] = newViewType;
}
public static void TriggerReload()
{
var roots = View.ActiveViews.Where (x => x.Parent == null).ToList();
foreach(var view in roots) {
Device.InvokeOnMainThread (view.Reload);
}
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
using System;
using Xunit;
namespace HotUI.Tests {
public class HotReloadTests {
public class MyOrgView : View {
public const string TextValue = "Hello!";
public MyOrgView ()
{
this.Body = () => new Text (TextValue);
}
}
public class MyNewView : View {
public const string TextValue = "Goodbye!";
public MyNewView ()
{
this.Body = () => new Text (TextValue);
}
}
public HotReloadTests ()
{
HotReloadHelper.IsEnabled = true;
}
[Fact]
public void HotReloadRegisterReplacedViewReplacesView ()
{
var orgView = new MyOrgView ();
var orgText = orgView.GetView () as Text;
Assert.Equal (MyOrgView.TextValue, orgText.Value);
HotReloadHelper.RegisterReplacedView (typeof (MyOrgView).FullName, typeof (MyNewView));
var newText = orgView.GetView () as Text;
Assert.Equal (MyNewView.TextValue, newText.Value);
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
using System;
using Xunit;
namespace HotUI.Tests {
public class HotReloadWithParameters : TestBase {
public class MyOrgView : View {
public const string TextValue = "Hello!";
public MyOrgView (string text)
{
HotReloadHelper.Register (this, text);
this.Body = () => new Text (text);
}
}
public class MyNewView : View {
public MyNewView (string text)
{
HotReloadHelper.Register (this, text);
this.Body = () => new Text (text);
}
}
public HotReloadWithParameters ()
{
HotReloadHelper.IsEnabled = true;
}
[Fact]
public void HotReloadRegisterReplacedViewReplacesView ()
{
var orgView = new MyOrgView (MyOrgView.TextValue);
var orgText = orgView.GetView () as Text;
Assert.Equal (MyOrgView.TextValue, orgText.Value);
HotReloadHelper.RegisterReplacedView (typeof (MyOrgView).FullName, typeof (MyNewView));
var newText = orgView.GetView () as Text;
Assert.Equal (MyOrgView.TextValue, newText.Value);
}
}
}