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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotUI.Tests", "tests\HotUI.Tests\HotUI.Tests.csproj", "{EA098E78-E56C-43B2-9D78-D4565EC04D09}"
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.WPF.Sample", "sample\HotUI.WPF.Sample\HotUI.WPF.Sample.csproj", "{0FC87DE5-B5E5-4846-894B-824497118143}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotUI.UWP", "src\HotUI.UWP\HotUI.UWP.csproj", "{2D337300-B3E9-4A36-8387-76A9A1100C5B}"
EndProject
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "HotUI.WPF", "src\HotUI.WPF\HotUI.WPF.csproj", "{A8D46A59-EFE4-434B-87EE-FC9085CF559A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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|iPhoneSimulator.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -290,10 +302,10 @@ Global
{BCAF5569-30DB-4D44-BF46-DFFE93DDCBD0} = {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}
{F8BA5DE0-AEC0-4423-B036-5F9157E939D0} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
{35664234-EB75-47BA-A082-9F96BDF6FC92} = {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}
{A8D46A59-EFE4-434B-87EE-FC9085CF559A} = {AB9AD206-4B1E-4B0C-88A2-5C769314E8A4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0653DB4A-5BBE-4D78-99B2-DB1C82663246}

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

@ -17,7 +17,10 @@ namespace HotUI.Android.Sample
protected override void OnCreate(Bundle 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.IsVisible = true;
_window.MakeKeyAndOrderFront(this);*/
}
//#if DEBUG
// HotUI.Reload.Init ();
//#endif
}
public override void WillTerminate(NSNotification notification)
{

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

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

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

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

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

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

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

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

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

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

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

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using HotUI.Services;
[assembly: InternalsVisibleTo ("HotUI.Tests")]
[assembly: InternalsVisibleTo ("HotUI.Reload.Tests")]
namespace HotUI{
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);
}
}
}