Fix line endings of all files in source/ except source/third_party/.

This was SVN commit r18558.
This commit is contained in:
elexis 2016-07-25 09:07:45 +00:00
parent ba25ffef74
commit bcbf25bfbd
28 changed files with 4942 additions and 4942 deletions

View File

@ -1,2 +1,2 @@
@cd ..\..\..\binaries\system
@pyrogenesis_dbg.exe -editor -actorviewer -mod=_test.minimal -mod=_test.collada
@cd ..\..\..\binaries\system
@pyrogenesis_dbg.exe -editor -actorviewer -mod=_test.minimal -mod=_test.collada

View File

@ -1,95 +1,95 @@
/* Copyright (c) 2013 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <AppKit/AppKit.h>
#import <AvailabilityMacros.h> // MAC_OS_X_VERSION_MIN_REQUIRED
#import <Foundation/Foundation.h>
#import <string>
#import "osx_pasteboard.h"
bool osx_GetStringFromPasteboard(std::string& out)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* string = nil;
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
// As of 10.6, pasteboards can hold multiple items
if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)])
{
NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
NSDictionary* options = [NSDictionary dictionary];
NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options];
// We only need to support a single item, so grab the first string
if (copiedItems != nil && [copiedItems count] > 0)
string = [copiedItems objectAtIndex:0];
else
return false; // No strings found on pasteboard
}
else
{
#endif // fallback to 10.5 API
// Verify that there is a string available for us
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
if ([pasteboard availableTypeFromArray:types] != nil)
string = [pasteboard stringForType:NSStringPboardType];
else
return false; // No strings found on pasteboard
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
}
#endif
if (string != nil)
out = std::string([string UTF8String]);
else
return false; // fail
return true; // success
}
bool osx_SendStringToPasteboard(const std::string& string)
{
// We're only working with strings, so we don't need to lazily write
// anything (otherwise we'd need to set up an owner and data provider)
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* type;
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
if ([pasteboard respondsToSelector: @selector(clearContents)])
{
type = NSPasteboardTypeString;
[pasteboard clearContents];
}
else
{
#endif // fallback to 10.5 API
type = NSStringPboardType;
NSArray* types = [NSArray arrayWithObjects: type, nil];
// Roughly equivalent to clearContents followed by addTypes:owner
[pasteboard declareTypes:types owner:nil];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
}
#endif
// May raise a NSPasteboardCommunicationException
BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type];
return ok == YES;
}
/* Copyright (c) 2013 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <AppKit/AppKit.h>
#import <AvailabilityMacros.h> // MAC_OS_X_VERSION_MIN_REQUIRED
#import <Foundation/Foundation.h>
#import <string>
#import "osx_pasteboard.h"
bool osx_GetStringFromPasteboard(std::string& out)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* string = nil;
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
// As of 10.6, pasteboards can hold multiple items
if ([pasteboard respondsToSelector: @selector(readObjectsForClasses:)])
{
NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
NSDictionary* options = [NSDictionary dictionary];
NSArray* copiedItems = [pasteboard readObjectsForClasses:classes options:options];
// We only need to support a single item, so grab the first string
if (copiedItems != nil && [copiedItems count] > 0)
string = [copiedItems objectAtIndex:0];
else
return false; // No strings found on pasteboard
}
else
{
#endif // fallback to 10.5 API
// Verify that there is a string available for us
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
if ([pasteboard availableTypeFromArray:types] != nil)
string = [pasteboard stringForType:NSStringPboardType];
else
return false; // No strings found on pasteboard
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
}
#endif
if (string != nil)
out = std::string([string UTF8String]);
else
return false; // fail
return true; // success
}
bool osx_SendStringToPasteboard(const std::string& string)
{
// We're only working with strings, so we don't need to lazily write
// anything (otherwise we'd need to set up an owner and data provider)
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSString* type;
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
if ([pasteboard respondsToSelector: @selector(clearContents)])
{
type = NSPasteboardTypeString;
[pasteboard clearContents];
}
else
{
#endif // fallback to 10.5 API
type = NSStringPboardType;
NSArray* types = [NSArray arrayWithObjects: type, nil];
// Roughly equivalent to clearContents followed by addTypes:owner
[pasteboard declareTypes:types owner:nil];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
}
#endif
// May raise a NSPasteboardCommunicationException
BOOL ok = [pasteboard setString:[NSString stringWithUTF8String:string.c_str()] forType:type];
return ok == YES;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,87 @@
@ECHO OFF
"%systemroot%\system32\cacls.exe" "%systemroot%\system32\config\system" >nul 2>&1
IF ERRORLEVEL 1 GOTO relaunch
REM detect whether OS is 32/64 bit
IF "%ProgramW6432%" == "%ProgramFiles%" (
SET aken_bits=64
) ELSE (
SET aken_bits=32
)
IF "%1" == "enabletest" GOTO enabletest
IF "%1" == "disabletest" GOTO disabletest
IF "%1" == "install" GOTO install
IF "%1" == "remove" GOTO remove
GOTO usage
:enabletest
bcdedit.exe /set TESTSIGNING ON
GOTO end
:disabletest
bcdedit.exe /set TESTSIGNING OFF
GOTO end
:install
IF (%2) == () (
SET aken_path="%~p0\aken%aken_bits%.sys"
) ELSE (
echo %2\aken%aken_bits%.sys
SET aken_path=%2\aken%aken_bits%.sys
)
echo %aken_path%
IF NOT EXIST %aken_path% GOTO notfound
sc create Aken DisplayName= Aken type= kernel start= auto binpath= %aken_path%
REM error= normal is default
IF ERRORLEVEL 1 GOTO failed
sc start Aken
IF ERRORLEVEL 1 GOTO failed
ECHO Success!
GOTO end
:remove
sc stop Aken
sc delete Aken
IF ERRORLEVEL 1 GOTO failed
ECHO Success! (The previous line should read: [SC] DeleteService SUCCESS)
GOTO end
:usage
ECHO To install the driver, please first enable test mode:
ECHO %0 enabletest
ECHO (This is necessary because Vista/Win7 x64 require signing with
ECHO a Microsoft "cross certificate". The Fraunhofer code signing certificate
ECHO is not enough, even though its chain of trust is impeccable.
ECHO Going the WHQL route, perhaps as an "unclassified" driver, might work.
ECHO see http://www.freeotfe.org/docs/Main/impact_of_kernel_driver_signing.htm )
ECHO Then reboot (!) and install the driver:
ECHO %0 install ["path_to_directory_containing_aken*.sys"]
ECHO (If no path is given, we will use the directory of this batch file)
ECHO To remove the driver and disable test mode, execute the following:
ECHO %0 remove
ECHO %0 disabletest
PAUSE
GOTO end
:relaunch
SET aken_vbs="%temp%\aken_run.vbs"
ECHO Set UAC = CreateObject^("Shell.Application"^) > %aken_vbs%
ECHO UAC.ShellExecute "cmd.exe", "/k %~s0 %1 %2", "", "runas", 1 >> %aken_vbs%
ECHO "To re-run this batch file as admin, we have created %aken_vbs% with the following contents:"
type %aken_vbs%
PAUSE
cscript //Nologo %aken_vbs%
DEL %aken_vbs%
GOTO end
:notfound
ECHO Driver not found at specified path (%aken_path%)
GOTO end
:failed
ECHO Something went wrong -- see previous line
GOTO end
@ECHO OFF
"%systemroot%\system32\cacls.exe" "%systemroot%\system32\config\system" >nul 2>&1
IF ERRORLEVEL 1 GOTO relaunch
REM detect whether OS is 32/64 bit
IF "%ProgramW6432%" == "%ProgramFiles%" (
SET aken_bits=64
) ELSE (
SET aken_bits=32
)
IF "%1" == "enabletest" GOTO enabletest
IF "%1" == "disabletest" GOTO disabletest
IF "%1" == "install" GOTO install
IF "%1" == "remove" GOTO remove
GOTO usage
:enabletest
bcdedit.exe /set TESTSIGNING ON
GOTO end
:disabletest
bcdedit.exe /set TESTSIGNING OFF
GOTO end
:install
IF (%2) == () (
SET aken_path="%~p0\aken%aken_bits%.sys"
) ELSE (
echo %2\aken%aken_bits%.sys
SET aken_path=%2\aken%aken_bits%.sys
)
echo %aken_path%
IF NOT EXIST %aken_path% GOTO notfound
sc create Aken DisplayName= Aken type= kernel start= auto binpath= %aken_path%
REM error= normal is default
IF ERRORLEVEL 1 GOTO failed
sc start Aken
IF ERRORLEVEL 1 GOTO failed
ECHO Success!
GOTO end
:remove
sc stop Aken
sc delete Aken
IF ERRORLEVEL 1 GOTO failed
ECHO Success! (The previous line should read: [SC] DeleteService SUCCESS)
GOTO end
:usage
ECHO To install the driver, please first enable test mode:
ECHO %0 enabletest
ECHO (This is necessary because Vista/Win7 x64 require signing with
ECHO a Microsoft "cross certificate". The Fraunhofer code signing certificate
ECHO is not enough, even though its chain of trust is impeccable.
ECHO Going the WHQL route, perhaps as an "unclassified" driver, might work.
ECHO see http://www.freeotfe.org/docs/Main/impact_of_kernel_driver_signing.htm )
ECHO Then reboot (!) and install the driver:
ECHO %0 install ["path_to_directory_containing_aken*.sys"]
ECHO (If no path is given, we will use the directory of this batch file)
ECHO To remove the driver and disable test mode, execute the following:
ECHO %0 remove
ECHO %0 disabletest
PAUSE
GOTO end
:relaunch
SET aken_vbs="%temp%\aken_run.vbs"
ECHO Set UAC = CreateObject^("Shell.Application"^) > %aken_vbs%
ECHO UAC.ShellExecute "cmd.exe", "/k %~s0 %1 %2", "", "runas", 1 >> %aken_vbs%
ECHO "To re-run this batch file as admin, we have created %aken_vbs% with the following contents:"
type %aken_vbs%
PAUSE
cscript //Nologo %aken_vbs%
DEL %aken_vbs%
GOTO end
:notfound
ECHO Driver not found at specified path (%aken_path%)
GOTO end
:failed
ECHO Something went wrong -- see previous line
GOTO end
:end

View File

@ -1,32 +1,32 @@
@echo off
REM ensure header is up to date (simpler than setting include path)
copy ..\..\..\..\..\include\lib\sysdep\os\win\aken\aken.h
cmd /c build_single.bat chk x64
cmd /c build_single.bat fre x64
cmd /c build_single.bat chk x86
REM must come last because each build_single.bat deletes aken.sys,
REM and that is the final output name of this step
cmd /c build_single.bat fre x86
cd amd64
copy /y aken64*.pdb ..\..\..\..\..\..\..\bin\x64
copy /y aken64*.sys ..\..\..\..\..\..\..\bin\x64
cd ..
cd i386
copy /y aken*.pdb ..\..\..\..\..\..\..\bin\Win32
copy /y aken*.sys ..\..\..\..\..\..\..\bin\Win32
cd ..
echo outputs copied to bin directory; will delete ALL output files after pressing a key
pause
if exist amd64 (rmdir /S /Q amd64)
if exist i386 (rmdir /S /Q i386)
if exist objchk_wnet_amd64 (rmdir /S /Q objchk_wnet_amd64)
if exist objfre_wnet_amd64 (rmdir /S /Q objfre_wnet_amd64)
if exist objchk_wnet_x86 (rmdir /S /Q objchk_wnet_x86)
if exist objfre_wnet_x86 (rmdir /S /Q objfre_wnet_x86)
if exist *.log (del /Q *.log)
if exist *.err (del /Q *.err)
@echo off
REM ensure header is up to date (simpler than setting include path)
copy ..\..\..\..\..\include\lib\sysdep\os\win\aken\aken.h
cmd /c build_single.bat chk x64
cmd /c build_single.bat fre x64
cmd /c build_single.bat chk x86
REM must come last because each build_single.bat deletes aken.sys,
REM and that is the final output name of this step
cmd /c build_single.bat fre x86
cd amd64
copy /y aken64*.pdb ..\..\..\..\..\..\..\bin\x64
copy /y aken64*.sys ..\..\..\..\..\..\..\bin\x64
cd ..
cd i386
copy /y aken*.pdb ..\..\..\..\..\..\..\bin\Win32
copy /y aken*.sys ..\..\..\..\..\..\..\bin\Win32
cd ..
echo outputs copied to bin directory; will delete ALL output files after pressing a key
pause
if exist amd64 (rmdir /S /Q amd64)
if exist i386 (rmdir /S /Q i386)
if exist objchk_wnet_amd64 (rmdir /S /Q objchk_wnet_amd64)
if exist objfre_wnet_amd64 (rmdir /S /Q objfre_wnet_amd64)
if exist objchk_wnet_x86 (rmdir /S /Q objchk_wnet_x86)
if exist objfre_wnet_x86 (rmdir /S /Q objfre_wnet_x86)
if exist *.log (del /Q *.log)
if exist *.err (del /Q *.err)
if exist *.wrn (del /Q *.wrn)

View File

@ -1,67 +1,67 @@
@echo off
REM build a single configuration ({chk,fre} x {x64,x64})
REM (must be in a separate file to allow invoking in a new cmd - otherwise,
REM setenv complains that the environment have already been set)
REM arguments: chk|fre x64|x86
if %1==chk (goto configOK)
if %1==fre (goto configOK)
echo first parameter must be either chk or fre
goto :eof
:configOK
if %2==x64 (goto archOK)
if %2==x86 (goto archOK)
echo second parameter must be either x64 or x64
goto :eof
:archOK
call C:\WinDDK\7600.16385.1\bin\setenv.bat C:\WinDDK\7600.16385.1\ %1 %2 WNET
e:
cd \FOM_Work\Programmierung\lowlevel\src\sysdep\os\win\aken
REM delete outputs to ensure they get rebuilt
if %2==x64 (goto delete64) else (goto delete32)
:delete64
if not exist amd64 (goto deleteEnd)
cd amd64
if exist aken.sys (del /Q aken.sys)
if exist aken.pdb (del /Q aken.pdb)
cd ..
goto deleteEnd
:delete32
if not exist i386 (goto deleteEnd)
cd i386
if exist aken.sys (del /Q aken.sys)
if exist aken.pdb (del /Q aken.pdb)
cd ..
goto deleteEnd
:deleteEnd
build
REM rename outputs in preparation for build_all's copying them to the binaries directories
if %2==x64 (goto rename64) else (goto rename32)
:rename64
if not exist amd64 (goto renameEnd)
cd amd64
if %1==chk (ren aken.pdb aken64d.pdb) else (ren aken.pdb aken64.pdb)
if %1==chk (ren aken.sys aken64d.sys) else (ren aken.sys aken64.sys)
cd ..
goto renameEnd
:rename32
if not exist i386 (goto renameEnd)
cd i386
if %1==chk (ren aken.pdb akend.pdb)
if %1==chk (ren aken.sys akend.sys)
cd ..
goto renameEnd
:renameEnd
@echo off
REM build a single configuration ({chk,fre} x {x64,x64})
REM (must be in a separate file to allow invoking in a new cmd - otherwise,
REM setenv complains that the environment have already been set)
REM arguments: chk|fre x64|x86
if %1==chk (goto configOK)
if %1==fre (goto configOK)
echo first parameter must be either chk or fre
goto :eof
:configOK
if %2==x64 (goto archOK)
if %2==x86 (goto archOK)
echo second parameter must be either x64 or x64
goto :eof
:archOK
call C:\WinDDK\7600.16385.1\bin\setenv.bat C:\WinDDK\7600.16385.1\ %1 %2 WNET
e:
cd \FOM_Work\Programmierung\lowlevel\src\sysdep\os\win\aken
REM delete outputs to ensure they get rebuilt
if %2==x64 (goto delete64) else (goto delete32)
:delete64
if not exist amd64 (goto deleteEnd)
cd amd64
if exist aken.sys (del /Q aken.sys)
if exist aken.pdb (del /Q aken.pdb)
cd ..
goto deleteEnd
:delete32
if not exist i386 (goto deleteEnd)
cd i386
if exist aken.sys (del /Q aken.sys)
if exist aken.pdb (del /Q aken.pdb)
cd ..
goto deleteEnd
:deleteEnd
build
REM rename outputs in preparation for build_all's copying them to the binaries directories
if %2==x64 (goto rename64) else (goto rename32)
:rename64
if not exist amd64 (goto renameEnd)
cd amd64
if %1==chk (ren aken.pdb aken64d.pdb) else (ren aken.pdb aken64.pdb)
if %1==chk (ren aken.sys aken64d.sys) else (ren aken.sys aken64.sys)
cd ..
goto renameEnd
:rename32
if not exist i386 (goto renameEnd)
cd i386
if %1==chk (ren aken.pdb akend.pdb)
if %1==chk (ren aken.sys akend.sys)
cd ..
goto renameEnd
:renameEnd

View File

@ -1,7 +1,7 @@
#
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def
#
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)\makefile.def

View File

@ -1,5 +1,5 @@
TARGETNAME=aken
TARGETPATH=.
TARGETTYPE=DRIVER
SOURCES=aken.c
TARGETNAME=aken
TARGETPATH=.
TARGETTYPE=DRIVER
SOURCES=aken.c

View File

@ -1 +1 @@
IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\ActorEditor.ico"
IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\ActorEditor.ico"

View File

@ -1 +1 @@
IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\$$PROJECT_NAME$$.ico"
IDI_ICON1 ICON "..\\AtlasUI\\Misc\\Graphics\\$$PROJECT_NAME$$.ico"

View File

@ -1,22 +1,22 @@
use strict;
use warnings;
my @files = qw( .cpp .rc );
my @progs = (
{
PROJECT_NAME => "ActorEditor",
WINDOW_NAME => "ActorEditor",
},
);
for my $p (@progs) {
for my $f (@files) {
open IN, "<", "_template$f" or die "Error opening _template$f: $!";
open OUT, ">", "$p->{PROJECT_NAME}$f" or die "Error opening $p->{PROJECT_NAME}$f: $!";
while (<IN>) {
s/\$\$([A-Z_]+)\$\$/ $p->{$1} /eg;
print OUT;
}
}
}
use strict;
use warnings;
my @files = qw( .cpp .rc );
my @progs = (
{
PROJECT_NAME => "ActorEditor",
WINDOW_NAME => "ActorEditor",
},
);
for my $p (@progs) {
for my $f (@files) {
open IN, "<", "_template$f" or die "Error opening _template$f: $!";
open OUT, ">", "$p->{PROJECT_NAME}$f" or die "Error opening $p->{PROJECT_NAME}$f: $!";
while (<IN>) {
s/\$\$([A-Z_]+)\$\$/ $p->{$1} /eg;
print OUT;
}
}
}

View File

@ -1,3 +1,3 @@
wxVirtualDirTreeCtrl (v1.0b) from http://www.solidsteel.nl/jorg/components/virtualdirtreectrl/wxVirtualDirTreeCtrl.php
wxVirtualDirTreeCtrl (v1.0b) from http://www.solidsteel.nl/jorg/components/virtualdirtreectrl/wxVirtualDirTreeCtrl.php
"wxVirtualDirTreeCtrl is freeware and distributed under the wxWidgets license."

View File

@ -1,36 +1,36 @@
/* XPM */
static const char *xpm_file[] = {
"16 16 16 2",
"00 c black",
"01 c #848484",
"02 c #D6D6CE",
"03 c gray100",
"04 c none",
"05 c gray100",
"06 c gray100",
"07 c gray100",
"08 c gray100",
"09 c gray100",
"10 c gray100",
"11 c gray100",
"12 c gray100",
"13 c gray100",
"14 c gray100",
"15 c gray100",
"04040404040404040404040404040404",
"04040101010101010101010104040404",
"04040103030303030303030001040404",
"04040103030303030303030002010404",
"04040103030000000000030000000004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030303030303030303030004",
"04040000000000000000000000000004"
};
/* XPM */
static const char *xpm_file[] = {
"16 16 16 2",
"00 c black",
"01 c #848484",
"02 c #D6D6CE",
"03 c gray100",
"04 c none",
"05 c gray100",
"06 c gray100",
"07 c gray100",
"08 c gray100",
"09 c gray100",
"10 c gray100",
"11 c gray100",
"12 c gray100",
"13 c gray100",
"14 c gray100",
"15 c gray100",
"04040404040404040404040404040404",
"04040101010101010101010104040404",
"04040103030303030303030001040404",
"04040103030303030303030002010404",
"04040103030000000000030000000004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030000000000000003030004",
"04040103030303030303030303030004",
"04040103030303030303030303030004",
"04040000000000000000000000000004"
};

View File

@ -1,36 +1,36 @@
/* XPM */
static const char *xpm_folder[] = {
"16 16 16 2",
"00 c #A06B04",
"01 c #B17B14",
"02 c #C18C25",
"03 c #CE9831",
"04 c gray29",
"05 c #777777",
"06 c gray55",
"07 c none",
"08 c #ECB94F",
"09 c #FFE178",
"10 c #FFF388",
"11 c #FFFB98",
"12 c #CECECE",
"13 c #DADADA",
"14 c gray97",
"15 c gray100",
"07070707070707070707070707070707",
"07070303030313140707070707070707",
"07031515151502121407070707070707",
"03151111111115020202010101131407",
"03111010101010151515151501061307",
"03100902020202020202020201010113",
"03090215151515151515151509150006",
"02090311111111111111111108110005",
"02090311111111111111111108110005",
"02090211101010101010101008110005",
"02090211090909090909090908110005",
"02090211090909090909090903110005",
"13010101010101000000000000000406",
"14120605050505050505050505050612",
"07141313131313131313131313131314",
"07070707070707070707070707070707"
};
/* XPM */
static const char *xpm_folder[] = {
"16 16 16 2",
"00 c #A06B04",
"01 c #B17B14",
"02 c #C18C25",
"03 c #CE9831",
"04 c gray29",
"05 c #777777",
"06 c gray55",
"07 c none",
"08 c #ECB94F",
"09 c #FFE178",
"10 c #FFF388",
"11 c #FFFB98",
"12 c #CECECE",
"13 c #DADADA",
"14 c gray97",
"15 c gray100",
"07070707070707070707070707070707",
"07070303030313140707070707070707",
"07031515151502121407070707070707",
"03151111111115020202010101131407",
"03111010101010151515151501061307",
"03100902020202020202020201010113",
"03090215151515151515151509150006",
"02090311111111111111111108110005",
"02090311111111111111111108110005",
"02090211101010101010101008110005",
"02090211090909090909090908110005",
"02090211090909090909090903110005",
"13010101010101000000000000000406",
"14120605050505050505050505050612",
"07141313131313131313131313131314",
"07070707070707070707070707070707"
};

View File

@ -1,36 +1,36 @@
/* XPM */
static const char *xpm_root[] = {
"16 16 16 2",
"00 c #976911",
"01 c gray42",
"02 c #C18B24",
"03 c #E3B552",
"04 c #6482B3",
"05 c #8E9CAA",
"06 c #FFEE86",
"07 c gray74",
"08 c #68BDFF",
"09 c #B9CDE6",
"10 c #D2D2D2",
"11 c #D8E9F1",
"12 c #E7F7FF",
"13 c #F3F7FB",
"14 c #F7FFFF",
"15 c none",
"15151515040404040404040811131515",
"15151515041313131313130808101315",
"15150202041309090909130808081013",
"15021313041313131313131111040111",
"02130606041209090909090912040110",
"02060606041212121212121212040110",
"02060202040707070707070707040207",
"02060213111313131313131313110001",
"02060206071109090909090911070001",
"02030206071111111111111111070001",
"02030206071111111111111111070001",
"02030206070707070707070707070001",
"02030206060303030303030303060001",
"11020202020000000000000000000101",
"13100101010101010101010101010110",
"15131110101010101010101010101113"
};
/* XPM */
static const char *xpm_root[] = {
"16 16 16 2",
"00 c #976911",
"01 c gray42",
"02 c #C18B24",
"03 c #E3B552",
"04 c #6482B3",
"05 c #8E9CAA",
"06 c #FFEE86",
"07 c gray74",
"08 c #68BDFF",
"09 c #B9CDE6",
"10 c #D2D2D2",
"11 c #D8E9F1",
"12 c #E7F7FF",
"13 c #F3F7FB",
"14 c #F7FFFF",
"15 c none",
"15151515040404040404040811131515",
"15151515041313131313130808101315",
"15150202041309090909130808081013",
"15021313041313131313131111040111",
"02130606041209090909090912040110",
"02060606041212121212121212040110",
"02060202040707070707070707040207",
"02060213111313131313131313110001",
"02060206071109090909090911070001",
"02030206071111111111111111070001",
"02030206071111111111111111070001",
"02030206070707070707070707070001",
"02030206060303030303030303060001",
"11020202020000000000000000000101",
"13100101010101010101010101010110",
"15131110101010101010101010101113"
};

View File

@ -1,7 +1,7 @@
ICON_ActorEditor ICON "Graphics\\ActorEditor.ico"
ICON_ArchiveViewer ICON "Graphics\\ArchiveViewer.ico"
ICON_FileConverter ICON "Graphics\\FileConverter.ico"
ICON_ScenarioEditor ICON "Graphics\\ScenarioEditor.ico"
#define wxUSE_NO_MANIFEST 1
#include "wx/msw/wx.rc"
ICON_ActorEditor ICON "Graphics\\ActorEditor.ico"
ICON_ArchiveViewer ICON "Graphics\\ArchiveViewer.ico"
ICON_FileConverter ICON "Graphics\\FileConverter.ico"
ICON_ScenarioEditor ICON "Graphics\\ScenarioEditor.ico"
#define wxUSE_NO_MANIFEST 1
#include "wx/msw/wx.rc"

View File

@ -1,93 +1,93 @@
wxWidgets:
* alt+f with notebook tab focussed
* wxLogTrace "%s" wxGetMessage (msw/window.cpp)
==============================
Colour tester:
==============================
Actor editor:
* Fix new actor fields
==============================
General and/or unsorted miscellany:
* Regression testing
* Open actors from prop-actor selection
* Make import undo work with multi-control windows, and update the title bar
===
* Better input controls (=> export nicely, support undo, etc)
* Help (tooltips, etc)
* More efficient Datafile::ReadList (don't read from disk every time)
* Version number
* Better colouring in test_dude.xml, where some variants have no data
* Tab to move between fields
===
* Test/fix when running with large fonts, or weird XP styles
* Better copy and paste (e.g. between multiple instances of the program,
and paste XML into other text-editing programs
* Save on exit: don't ask if no changes
* Browse for meshes/actors/etc, in a more nice/correct/unbuggy way
* Window size memory
* Column width memory
* Allow reset memory
* Input validation?
* Browse for meshes/actors/etc, with mods
* Customised AtObj->string conversion
* Undo in text-editing boxes
* AtlasObjectXML error handling
* Use standard wxWidgets 2.5.4 release
* Fix Escape in combo boxes inside dialogs
* Make wxFileHistory work when not at the very end of a menu
===
* Document lots
* wxListItemAttr* OnGetItemAttr(long item) >>>const<<< - wx documentation wrong?
* Better column widths / window sizes
* Don't create a row when editing a blank one then removing focus without typing anything
======
Done: (newest at top)
* Display DDS info (compression, size)
* Copy and paste
* 'Create entity' button (take name, parent)
* Correct undo menu entry when selecting 'New' (so it's not 'Import')
* MRU file list
* 'New' menu item
* Import/export filter, for validation(?) and for handling attributes
wxWidgets:
* alt+f with notebook tab focussed
* wxLogTrace "%s" wxGetMessage (msw/window.cpp)
==============================
Colour tester:
==============================
Actor editor:
* Fix new actor fields
==============================
General and/or unsorted miscellany:
* Regression testing
* Open actors from prop-actor selection
* Make import undo work with multi-control windows, and update the title bar
===
* Better input controls (=> export nicely, support undo, etc)
* Help (tooltips, etc)
* More efficient Datafile::ReadList (don't read from disk every time)
* Version number
* Better colouring in test_dude.xml, where some variants have no data
* Tab to move between fields
===
* Test/fix when running with large fonts, or weird XP styles
* Better copy and paste (e.g. between multiple instances of the program,
and paste XML into other text-editing programs
* Save on exit: don't ask if no changes
* Browse for meshes/actors/etc, in a more nice/correct/unbuggy way
* Window size memory
* Column width memory
* Allow reset memory
* Input validation?
* Browse for meshes/actors/etc, with mods
* Customised AtObj->string conversion
* Undo in text-editing boxes
* AtlasObjectXML error handling
* Use standard wxWidgets 2.5.4 release
* Fix Escape in combo boxes inside dialogs
* Make wxFileHistory work when not at the very end of a menu
===
* Document lots
* wxListItemAttr* OnGetItemAttr(long item) >>>const<<< - wx documentation wrong?
* Better column widths / window sizes
* Don't create a row when editing a blank one then removing focus without typing anything
======
Done: (newest at top)
* Display DDS info (compression, size)
* Copy and paste
* 'Create entity' button (take name, parent)
* Correct undo menu entry when selecting 'New' (so it's not 'Import')
* MRU file list
* 'New' menu item
* Import/export filter, for validation(?) and for handling attributes

View File

@ -1,88 +1,88 @@
use strict;
use warnings;
use File::Find;
my $dir = '../../../binaries/data/mods/official/entities';
my @xml;
find({wanted=>sub{
push @xml, $_ if /\.xml$/;
}, no_chdir=>1}, $dir);
s~\Q$dir/~~ for @xml;
my %nodes;
for my $f (@xml) {
$f =~ m~(?:.*/|^)(.*)\.xml~ or die "invalid filename $f";
my $name = $1;
open I, "$dir/$f" or die "error opening $dir/$f: $!";
my $data = do { local $/; <I> };
close I;
my $parent;
$parent = $1 if $data =~ /Parent="(.*?)"/;
my ($upgrade, $rank);
$upgrade = $1 if $data =~ /<Entity>\s*(.*?)\s*</s;
$rank = $1 if $data =~ /Up.*rank="(.*?)"/s;
my $actor;
$actor = $1 if $data =~ /<Actor>\s*(.*?)\s*</;
undef $upgrade unless defined $upgrade and length $upgrade;
$nodes{$parent} ||= {} if defined $parent;
$nodes{$upgrade} ||= {} if defined $upgrade;
$nodes{$name} = { def=>1, parent=>$parent, upgrade=>[$upgrade, $rank], actor=>$actor };
}
open O, ">", "entities.dot" or die $!;
print O <<EOF;
digraph g
{
graph [nodesep=.1];
edge [fontname=ArialN fontsize=8];
EOF
print O " /* entities without actors */
node [fontname=ArialN fontsize=10 shape=ellipse];
";
for (sort keys %nodes) {
print O qq{ "$_";\n} if keys %{$nodes{$_}} and not defined $nodes{$_}{actor};
}
print O " /* entities with actors */
node [shape=box];
";
for (sort keys %nodes) {
print O qq{ "$_";\n} if keys %{$nodes{$_}} and defined $nodes{$_}{actor};
}
print O " /* undefined entities */
node [color=red];
";
for (sort keys %nodes) {
print O qq{ "$_";\n} if not keys %{$nodes{$_}};
}
print O "\n /* inheritance edges */\n";
for (sort keys %nodes) {
print O qq{ "$nodes{$_}{parent}" -> "$_";\n} if defined $nodes{$_}{parent};
}
print O "\n /* upgrade edges */\n";
print O " edge [color=red fontcolor=red]\n";
for (sort keys %nodes) {
if (defined $nodes{$_}{upgrade}[0]) {
print O qq{ "$_" -> "$nodes{$_}{upgrade}[0]"};
print O qq{ [label="from rank $nodes{$_}{upgrade}[1]"]} if defined $nodes{$_}{upgrade}[1];
print O qq{;\n};
}
}
print O "}\n";
close O;
use strict;
use warnings;
use File::Find;
my $dir = '../../../binaries/data/mods/official/entities';
my @xml;
find({wanted=>sub{
push @xml, $_ if /\.xml$/;
}, no_chdir=>1}, $dir);
s~\Q$dir/~~ for @xml;
my %nodes;
for my $f (@xml) {
$f =~ m~(?:.*/|^)(.*)\.xml~ or die "invalid filename $f";
my $name = $1;
open I, "$dir/$f" or die "error opening $dir/$f: $!";
my $data = do { local $/; <I> };
close I;
my $parent;
$parent = $1 if $data =~ /Parent="(.*?)"/;
my ($upgrade, $rank);
$upgrade = $1 if $data =~ /<Entity>\s*(.*?)\s*</s;
$rank = $1 if $data =~ /Up.*rank="(.*?)"/s;
my $actor;
$actor = $1 if $data =~ /<Actor>\s*(.*?)\s*</;
undef $upgrade unless defined $upgrade and length $upgrade;
$nodes{$parent} ||= {} if defined $parent;
$nodes{$upgrade} ||= {} if defined $upgrade;
$nodes{$name} = { def=>1, parent=>$parent, upgrade=>[$upgrade, $rank], actor=>$actor };
}
open O, ">", "entities.dot" or die $!;
print O <<EOF;
digraph g
{
graph [nodesep=.1];
edge [fontname=ArialN fontsize=8];
EOF
print O " /* entities without actors */
node [fontname=ArialN fontsize=10 shape=ellipse];
";
for (sort keys %nodes) {
print O qq{ "$_";\n} if keys %{$nodes{$_}} and not defined $nodes{$_}{actor};
}
print O " /* entities with actors */
node [shape=box];
";
for (sort keys %nodes) {
print O qq{ "$_";\n} if keys %{$nodes{$_}} and defined $nodes{$_}{actor};
}
print O " /* undefined entities */
node [color=red];
";
for (sort keys %nodes) {
print O qq{ "$_";\n} if not keys %{$nodes{$_}};
}
print O "\n /* inheritance edges */\n";
for (sort keys %nodes) {
print O qq{ "$nodes{$_}{parent}" -> "$_";\n} if defined $nodes{$_}{parent};
}
print O "\n /* upgrade edges */\n";
print O " edge [color=red fontcolor=red]\n";
for (sort keys %nodes) {
if (defined $nodes{$_}{upgrade}[0]) {
print O qq{ "$_" -> "$nodes{$_}{upgrade}[0]"};
print O qq{ [label="from rank $nodes{$_}{upgrade}[1]"]} if defined $nodes{$_}{upgrade}[1];
print O qq{;\n};
}
}
print O "}\n";
close O;
system("dot.exe", "-Tpng", "entities.dot", "-o", "entities.png");

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,72 @@
0 A.D. Font Builder
====================
The Font Builder generates pre-rendered font glyphs for use in the game engine. Its output for each font consists
of an 8-bit greyscale PNG image and a descriptor .fnt file that describes and locates each individual glyph in the image
(see fileformat.txt for details).
See the wiki page for more information:
http://trac.wildfiregames.com/wiki/Font_Builder2
Prerequisites
-------------
The main prerequisite for the fontbuilder is the Cairo imaging library and its Python bindings, PyCairo. On most
Linux distributions, this should be merely a matter of installing a package (e.g. 'python-cairo' for Debian/Ubuntu),
but on Windows it's more involved.
We'll demonstrate the process for Windows 32-bit first. Grab a Win32 binary for PyCairo from
http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/
and install it using the installer. There are installers available for Python versions 2.6 and 2.7. The installer
extracts the necessary files into Lib\site-packages\cairo within your Python installation directory.
Next is Cairo itself, and some dependencies which are required for Cairo to work. Head to
http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/
and get the following binaries. Listed next to each are their version numbers at the time of writing; these may vary
over time, so be adaptive!
- Cairo (cairo_1.8.10-3_win32.zip)
- Fontconfig (fontconfig_2.8.0-2_win32.zip)
- Freetype (freetype_2.4.4-1_win32.zip)
- Expat (expat_2.0.1-1_win32.zip)
- libpng (libpng_1.4.3-1_win32.zip)
- zlib (zlib_1.2.5-2_win32.zip).
Each ZIP file will contain a bin subfolder with a DLL file in it. Put the following DLLs in Lib\site-packages\cairo
within your Python installation:
libcairo-2.dll (from cairo_1.8.10-3_win32.zip)
libfontconfig-1.dll (from fontconfig_2.8.0-2_win32.zip)
freetype6.dll (from freetype_2.4.4-1_win32.zip)
libexpat-1.dll (from expat_2.0.1-1_win32.zip)
libpng14-14.dll (from libpng_1.4.3-1_win32.zip)
zlib1.dll (from zlib_1.2.5-2_win32.zip).
You should be all set now. To test whether PyCairo installed successfully, try running the following command on a
command line:
python -c "import cairo"
If it doesn't complain, then it's installed successfully.
On Windows 64-bit, the process is similar, but no pre-built PyCairo executable appears to be available from Gnome at
the time of writing. Instead, you can install PyGTK+ for 64-bit Windows, which includes Cairo, PyCairo, and the
same set of dependencies. See this page for details:
http://www.pygtk.org/downloads.html
Running
-------
Running the font-builder is fairly straight-forward; there are no configuration options. One caveat is that you must
run it from its own directory as the current directory.
python fontbuilder.py
This will generate the output .png and .fnt files straight into the binaries/data/mods/public/fonts directory, ready
for in-game use.
0 A.D. Font Builder
====================
The Font Builder generates pre-rendered font glyphs for use in the game engine. Its output for each font consists
of an 8-bit greyscale PNG image and a descriptor .fnt file that describes and locates each individual glyph in the image
(see fileformat.txt for details).
See the wiki page for more information:
http://trac.wildfiregames.com/wiki/Font_Builder2
Prerequisites
-------------
The main prerequisite for the fontbuilder is the Cairo imaging library and its Python bindings, PyCairo. On most
Linux distributions, this should be merely a matter of installing a package (e.g. 'python-cairo' for Debian/Ubuntu),
but on Windows it's more involved.
We'll demonstrate the process for Windows 32-bit first. Grab a Win32 binary for PyCairo from
http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/
and install it using the installer. There are installers available for Python versions 2.6 and 2.7. The installer
extracts the necessary files into Lib\site-packages\cairo within your Python installation directory.
Next is Cairo itself, and some dependencies which are required for Cairo to work. Head to
http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/
and get the following binaries. Listed next to each are their version numbers at the time of writing; these may vary
over time, so be adaptive!
- Cairo (cairo_1.8.10-3_win32.zip)
- Fontconfig (fontconfig_2.8.0-2_win32.zip)
- Freetype (freetype_2.4.4-1_win32.zip)
- Expat (expat_2.0.1-1_win32.zip)
- libpng (libpng_1.4.3-1_win32.zip)
- zlib (zlib_1.2.5-2_win32.zip).
Each ZIP file will contain a bin subfolder with a DLL file in it. Put the following DLLs in Lib\site-packages\cairo
within your Python installation:
libcairo-2.dll (from cairo_1.8.10-3_win32.zip)
libfontconfig-1.dll (from fontconfig_2.8.0-2_win32.zip)
freetype6.dll (from freetype_2.4.4-1_win32.zip)
libexpat-1.dll (from expat_2.0.1-1_win32.zip)
libpng14-14.dll (from libpng_1.4.3-1_win32.zip)
zlib1.dll (from zlib_1.2.5-2_win32.zip).
You should be all set now. To test whether PyCairo installed successfully, try running the following command on a
command line:
python -c "import cairo"
If it doesn't complain, then it's installed successfully.
On Windows 64-bit, the process is similar, but no pre-built PyCairo executable appears to be available from Gnome at
the time of writing. Instead, you can install PyGTK+ for 64-bit Windows, which includes Cairo, PyCairo, and the
same set of dependencies. See this page for details:
http://www.pygtk.org/downloads.html
Running
-------
Running the font-builder is fairly straight-forward; there are no configuration options. One caveat is that you must
run it from its own directory as the current directory.
python fontbuilder.py
This will generate the output .png and .fnt files straight into the binaries/data/mods/public/fonts directory, ready
for in-game use.

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
' Simple VBScript to open the 0 A.D. logs folder
' the path varies on different versions of Windows
Set objShell = CreateObject("Shell.Application")
' 0x1C is equivalent to the ssfLOCALAPPDATA constant in VB
objShell.Explore objShell.Namespace(&H1C&).Self.Path & "\0ad\logs"
' Simple VBScript to open the 0 A.D. logs folder
' the path varies on different versions of Windows
Set objShell = CreateObject("Shell.Application")
' 0x1C is equivalent to the ssfLOCALAPPDATA constant in VB
objShell.Explore objShell.Namespace(&H1C&).Self.Path & "\0ad\logs"

View File

@ -1,426 +1,426 @@
// Copyright (c) 2016 Wildfire Games
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Profiler2Report module
// Create one instance per profiler report you wish to open.
// This gives you the interface to access the raw and processed data
var Profiler2Report = function(callback, tryLive, file)
{
var outInterface = {};
// Item types returned by the engine
var ITEM_EVENT = 1;
var ITEM_ENTER = 2;
var ITEM_LEAVE = 3;
var ITEM_ATTRIBUTE = 4;
var g_used_colours = {};
var g_raw_data;
var g_data;
function refresh(callback, tryLive, file)
{
if (tryLive)
refresh_live(callback, file);
else
refresh_jsonp(callback, file);
}
outInterface.refresh = refresh;
function refresh_jsonp(callback, source)
{
if (!source)
{
callback(false);
return
}
var reader = new FileReader();
reader.onload = function(e)
{
refresh_from_jsonp(callback, e.target.result);
}
reader.onerror = function(e) {
alert("Failed to load report file");
callback(false);
return;
}
reader.readAsText(source);
}
function refresh_from_jsonp(callback, content)
{
var script = document.createElement('script');
window.profileDataCB = function(data)
{
script.parentNode.removeChild(script);
var threads = [];
data.threads.forEach(function(thread) {
var canvas = $('<canvas width="1600" height="160"></canvas>');
threads.push({'name': thread.name, 'data': { 'events': concat_events(thread.data) }, 'canvas': canvas.get(0)});
});
g_raw_data = { 'threads': threads };
compute_data();
callback(true);
};
script.innerHTML = content;
document.body.appendChild(script);
}
function refresh_live(callback, file)
{
$.ajax({
url: 'http://127.0.0.1:8000/overview',
dataType: 'json',
success: function (data) {
var threads = [];
data.threads.forEach(function(thread) {
threads.push({'name': thread.name});
});
var callback_data = { 'threads': threads, 'completed': 0 };
threads.forEach(function(thread) {
refresh_thread(callback, thread, callback_data);
});
},
error: function (jqXHR, textStatus, errorThrown)
{
console.log('Failed to connect to server ("'+textStatus+'")');
callback(false);
}
});
}
function refresh_thread(callback, thread, callback_data)
{
$.ajax({
url: 'http://127.0.0.1:8000/query',
dataType: 'json',
data: { 'thread': thread.name },
success: function (data) {
data.events = concat_events(data);
thread.data = data;
if (++callback_data.completed == callback_data.threads.length)
{
g_raw_data = { 'threads': callback_data.threads };
compute_data();
callback(true);
}
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Failed to connect to server ("'+textStatus+'")');
}
});
}
function compute_data(range)
{
g_data = { "threads" : [] };
g_data_by_frame = { "threads" : [] };
for (let thread = 0; thread < g_raw_data.threads.length; thread++)
{
let processed_data = process_raw_data(g_raw_data.threads[thread].data.events, range );
if (!processed_data.intervals.length && !processed_data.events.length)
continue;
g_data.threads[thread] = processed_data;
g_data.threads[thread].intervals_by_type_frame = {};
if (!g_data.threads[thread].frames.length)
continue
// compute intervals by types and frames if there are frames.
for (let type in g_data.threads[thread].intervals_by_type)
{
let current_frame = 0;
g_data.threads[thread].intervals_by_type_frame[type] = [[]];
for (let i = 0; i < g_data.threads[thread].intervals_by_type[type].length;i++)
{
let event = g_data.threads[thread].intervals[g_data.threads[thread].intervals_by_type[type][i]];
while (current_frame < g_data.threads[thread].frames.length && event.t0 > g_data.threads[thread].frames[current_frame].t1)
{
g_data.threads[thread].intervals_by_type_frame[type].push([]);
current_frame++;
}
if (current_frame < g_data.threads[thread].frames.length)
g_data.threads[thread].intervals_by_type_frame[type][current_frame].push(g_data.threads[thread].intervals_by_type[type][i]);
}
}
};
}
function process_raw_data(data, range)
{
if (!data.length)
return { 'frames': [], 'events': [], 'intervals': [], 'intervals_by_type' : {}, 'tmin': 0, 'tmax': 0 };
var start, end;
var tmin, tmax;
var frames = [];
var last_frame_time_start = undefined;
var last_frame_time_end = undefined;
var stack = [];
for (var i = 0; i < data.length; ++i)
{
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
{
if (last_frame_time_end)
frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end});
last_frame_time_start = data[i][1];
}
if (data[i][0] == ITEM_ENTER)
stack.push(data[i][2]);
if (data[i][0] == ITEM_LEAVE)
{
if (stack[stack.length-1] == 'frame')
last_frame_time_end = data[i][1];
stack.pop();
}
}
if(!range)
{
range = { "tmin" : data[0][1], "tmax" : data[data.length-1][1] };
}
if (range.numframes)
{
for (var i = data.length - 1; i > 0; --i)
{
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
{
end = i;
break;
}
}
var framesfound = 0;
for (var i = end - 1; i > 0; --i)
{
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
{
start = i;
if (++framesfound == range.numframes)
break;
}
}
tmin = data[start][1];
tmax = data[end][1];
}
else if (range.seconds)
{
var end = data.length - 1;
for (var i = end; i > 0; --i)
{
var type = data[i][0];
if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE)
{
tmax = data[i][1];
break;
}
}
tmin = tmax - range.seconds;
for (var i = end; i > 0; --i)
{
var type = data[i][0];
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin)
break;
start = i;
}
}
else
{
start = 0;
end = data.length - 1;
tmin = range.tmin;
tmax = range.tmax;
for (var i = data.length-1; i > 0; --i)
{
var type = data[i][0];
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax)
{
end = i;
break;
}
}
for (var i = end; i > 0; --i)
{
var type = data[i][0];
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin)
break;
start = i;
}
// Move the start/end outwards by another frame, so we don't lose data at the edges
while (start > 0)
{
--start;
if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart')
break;
}
while (end < data.length-1)
{
++end;
if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart')
break;
}
}
var num_colours = 0;
var events = [];
// Read events for the entire data period (not just start..end)
var lastWasEvent = false;
for (var i = 0; i < data.length; ++i)
{
if (data[i][0] == ITEM_EVENT)
{
events.push({'t': data[i][1], 'id': data[i][2]});
lastWasEvent = true;
}
else if (data[i][0] == ITEM_ATTRIBUTE)
{
if (lastWasEvent)
{
if (!events[events.length-1].attrs)
events[events.length-1].attrs = [];
events[events.length-1].attrs.push(data[i][1]);
}
}
else
{
lastWasEvent = false;
}
}
var intervals = [];
var intervals_by_type = {};
// Read intervals from the focused data period (start..end)
stack = [];
var lastT = 0;
var lastWasEvent = false;
for (var i = start; i <= end; ++i)
{
if (data[i][0] == ITEM_EVENT)
{
// if (data[i][1] < lastT)
// console.log('Time went backwards: ' + (data[i][1] - lastT));
lastT = data[i][1];
lastWasEvent = true;
}
else if (data[i][0] == ITEM_ENTER)
{
// if (data[i][1] < lastT)
// console.log('Time - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
stack.push({'t0': data[i][1], 'id': data[i][2]});
lastT = data[i][1];
lastWasEvent = false;
}
else if (data[i][0] == ITEM_LEAVE)
{
// if (data[i][1] < lastT)
// console.log('Time - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
lastT = data[i][1];
lastWasEvent = false;
if (!stack.length)
continue;
var interval = stack.pop();
if (!g_used_colours[interval.id])
g_used_colours[interval.id] = new_colour(num_colours++);
interval.colour = g_used_colours[interval.id];
interval.t1 = data[i][1];
interval.duration = interval.t1 - interval.t0;
interval.depth = stack.length;
//console.log(JSON.stringify(interval));
intervals.push(interval);
if (interval.id in intervals_by_type)
intervals_by_type[interval.id].push(intervals.length-1);
else
intervals_by_type[interval.id] = [intervals.length-1];
if (interval.id == "Script" && interval.attrs && interval.attrs.length)
{
let curT = interval.t0;
for (let subItem in interval.attrs)
{
let sub = interval.attrs[subItem];
if (sub.search("buffer") != -1)
continue;
let newInterv = {};
newInterv.t0 = curT;
newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000;
if (!newInterv.duration)
continue;
newInterv.t1 = curT + newInterv.duration;
curT += newInterv.duration;
newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1");
newInterv.colour = g_used_colours[interval.id];
newInterv.depth = interval.depth+1;
intervals.push(newInterv);
if (newInterv.id in intervals_by_type)
intervals_by_type[newInterv.id].push(intervals.length-1);
else
intervals_by_type[newInterv.id] = [intervals.length-1];
}
}
}
else if (data[i][0] == ITEM_ATTRIBUTE)
{
if (!lastWasEvent && stack.length)
{
if (!stack[stack.length-1].attrs)
stack[stack.length-1].attrs = [];
stack[stack.length-1].attrs.push(data[i][1]);
}
}
}
return { 'frames': frames, 'events': events, 'intervals': intervals, 'intervals_by_type' : intervals_by_type, 'tmin': tmin, 'tmax': tmax };
}
outInterface.data = function() { return g_data; };
outInterface.raw_data = function() { return g_raw_data; };
outInterface.data_by_frame = function() { return g_data_by_frame; };
refresh(callback, tryLive, file);
return outInterface;
};
// Copyright (c) 2016 Wildfire Games
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Profiler2Report module
// Create one instance per profiler report you wish to open.
// This gives you the interface to access the raw and processed data
var Profiler2Report = function(callback, tryLive, file)
{
var outInterface = {};
// Item types returned by the engine
var ITEM_EVENT = 1;
var ITEM_ENTER = 2;
var ITEM_LEAVE = 3;
var ITEM_ATTRIBUTE = 4;
var g_used_colours = {};
var g_raw_data;
var g_data;
function refresh(callback, tryLive, file)
{
if (tryLive)
refresh_live(callback, file);
else
refresh_jsonp(callback, file);
}
outInterface.refresh = refresh;
function refresh_jsonp(callback, source)
{
if (!source)
{
callback(false);
return
}
var reader = new FileReader();
reader.onload = function(e)
{
refresh_from_jsonp(callback, e.target.result);
}
reader.onerror = function(e) {
alert("Failed to load report file");
callback(false);
return;
}
reader.readAsText(source);
}
function refresh_from_jsonp(callback, content)
{
var script = document.createElement('script');
window.profileDataCB = function(data)
{
script.parentNode.removeChild(script);
var threads = [];
data.threads.forEach(function(thread) {
var canvas = $('<canvas width="1600" height="160"></canvas>');
threads.push({'name': thread.name, 'data': { 'events': concat_events(thread.data) }, 'canvas': canvas.get(0)});
});
g_raw_data = { 'threads': threads };
compute_data();
callback(true);
};
script.innerHTML = content;
document.body.appendChild(script);
}
function refresh_live(callback, file)
{
$.ajax({
url: 'http://127.0.0.1:8000/overview',
dataType: 'json',
success: function (data) {
var threads = [];
data.threads.forEach(function(thread) {
threads.push({'name': thread.name});
});
var callback_data = { 'threads': threads, 'completed': 0 };
threads.forEach(function(thread) {
refresh_thread(callback, thread, callback_data);
});
},
error: function (jqXHR, textStatus, errorThrown)
{
console.log('Failed to connect to server ("'+textStatus+'")');
callback(false);
}
});
}
function refresh_thread(callback, thread, callback_data)
{
$.ajax({
url: 'http://127.0.0.1:8000/query',
dataType: 'json',
data: { 'thread': thread.name },
success: function (data) {
data.events = concat_events(data);
thread.data = data;
if (++callback_data.completed == callback_data.threads.length)
{
g_raw_data = { 'threads': callback_data.threads };
compute_data();
callback(true);
}
},
error: function (jqXHR, textStatus, errorThrown) {
alert('Failed to connect to server ("'+textStatus+'")');
}
});
}
function compute_data(range)
{
g_data = { "threads" : [] };
g_data_by_frame = { "threads" : [] };
for (let thread = 0; thread < g_raw_data.threads.length; thread++)
{
let processed_data = process_raw_data(g_raw_data.threads[thread].data.events, range );
if (!processed_data.intervals.length && !processed_data.events.length)
continue;
g_data.threads[thread] = processed_data;
g_data.threads[thread].intervals_by_type_frame = {};
if (!g_data.threads[thread].frames.length)
continue
// compute intervals by types and frames if there are frames.
for (let type in g_data.threads[thread].intervals_by_type)
{
let current_frame = 0;
g_data.threads[thread].intervals_by_type_frame[type] = [[]];
for (let i = 0; i < g_data.threads[thread].intervals_by_type[type].length;i++)
{
let event = g_data.threads[thread].intervals[g_data.threads[thread].intervals_by_type[type][i]];
while (current_frame < g_data.threads[thread].frames.length && event.t0 > g_data.threads[thread].frames[current_frame].t1)
{
g_data.threads[thread].intervals_by_type_frame[type].push([]);
current_frame++;
}
if (current_frame < g_data.threads[thread].frames.length)
g_data.threads[thread].intervals_by_type_frame[type][current_frame].push(g_data.threads[thread].intervals_by_type[type][i]);
}
}
};
}
function process_raw_data(data, range)
{
if (!data.length)
return { 'frames': [], 'events': [], 'intervals': [], 'intervals_by_type' : {}, 'tmin': 0, 'tmax': 0 };
var start, end;
var tmin, tmax;
var frames = [];
var last_frame_time_start = undefined;
var last_frame_time_end = undefined;
var stack = [];
for (var i = 0; i < data.length; ++i)
{
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
{
if (last_frame_time_end)
frames.push({'t0': last_frame_time_start, 't1': last_frame_time_end});
last_frame_time_start = data[i][1];
}
if (data[i][0] == ITEM_ENTER)
stack.push(data[i][2]);
if (data[i][0] == ITEM_LEAVE)
{
if (stack[stack.length-1] == 'frame')
last_frame_time_end = data[i][1];
stack.pop();
}
}
if(!range)
{
range = { "tmin" : data[0][1], "tmax" : data[data.length-1][1] };
}
if (range.numframes)
{
for (var i = data.length - 1; i > 0; --i)
{
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
{
end = i;
break;
}
}
var framesfound = 0;
for (var i = end - 1; i > 0; --i)
{
if (data[i][0] == ITEM_EVENT && data[i][2] == '__framestart')
{
start = i;
if (++framesfound == range.numframes)
break;
}
}
tmin = data[start][1];
tmax = data[end][1];
}
else if (range.seconds)
{
var end = data.length - 1;
for (var i = end; i > 0; --i)
{
var type = data[i][0];
if (type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE)
{
tmax = data[i][1];
break;
}
}
tmin = tmax - range.seconds;
for (var i = end; i > 0; --i)
{
var type = data[i][0];
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin)
break;
start = i;
}
}
else
{
start = 0;
end = data.length - 1;
tmin = range.tmin;
tmax = range.tmax;
for (var i = data.length-1; i > 0; --i)
{
var type = data[i][0];
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmax)
{
end = i;
break;
}
}
for (var i = end; i > 0; --i)
{
var type = data[i][0];
if ((type == ITEM_EVENT || type == ITEM_ENTER || type == ITEM_LEAVE) && data[i][1] < tmin)
break;
start = i;
}
// Move the start/end outwards by another frame, so we don't lose data at the edges
while (start > 0)
{
--start;
if (data[start][0] == ITEM_EVENT && data[start][2] == '__framestart')
break;
}
while (end < data.length-1)
{
++end;
if (data[end][0] == ITEM_EVENT && data[end][2] == '__framestart')
break;
}
}
var num_colours = 0;
var events = [];
// Read events for the entire data period (not just start..end)
var lastWasEvent = false;
for (var i = 0; i < data.length; ++i)
{
if (data[i][0] == ITEM_EVENT)
{
events.push({'t': data[i][1], 'id': data[i][2]});
lastWasEvent = true;
}
else if (data[i][0] == ITEM_ATTRIBUTE)
{
if (lastWasEvent)
{
if (!events[events.length-1].attrs)
events[events.length-1].attrs = [];
events[events.length-1].attrs.push(data[i][1]);
}
}
else
{
lastWasEvent = false;
}
}
var intervals = [];
var intervals_by_type = {};
// Read intervals from the focused data period (start..end)
stack = [];
var lastT = 0;
var lastWasEvent = false;
for (var i = start; i <= end; ++i)
{
if (data[i][0] == ITEM_EVENT)
{
// if (data[i][1] < lastT)
// console.log('Time went backwards: ' + (data[i][1] - lastT));
lastT = data[i][1];
lastWasEvent = true;
}
else if (data[i][0] == ITEM_ENTER)
{
// if (data[i][1] < lastT)
// console.log('Time - ENTER went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
stack.push({'t0': data[i][1], 'id': data[i][2]});
lastT = data[i][1];
lastWasEvent = false;
}
else if (data[i][0] == ITEM_LEAVE)
{
// if (data[i][1] < lastT)
// console.log('Time - LEAVE went backwards: ' + (data[i][1] - lastT) + " - " + JSON.stringify(data[i]));
lastT = data[i][1];
lastWasEvent = false;
if (!stack.length)
continue;
var interval = stack.pop();
if (!g_used_colours[interval.id])
g_used_colours[interval.id] = new_colour(num_colours++);
interval.colour = g_used_colours[interval.id];
interval.t1 = data[i][1];
interval.duration = interval.t1 - interval.t0;
interval.depth = stack.length;
//console.log(JSON.stringify(interval));
intervals.push(interval);
if (interval.id in intervals_by_type)
intervals_by_type[interval.id].push(intervals.length-1);
else
intervals_by_type[interval.id] = [intervals.length-1];
if (interval.id == "Script" && interval.attrs && interval.attrs.length)
{
let curT = interval.t0;
for (let subItem in interval.attrs)
{
let sub = interval.attrs[subItem];
if (sub.search("buffer") != -1)
continue;
let newInterv = {};
newInterv.t0 = curT;
newInterv.duration = +sub.replace(/.+? ([.0-9]+)us/, "$1")/1000000;
if (!newInterv.duration)
continue;
newInterv.t1 = curT + newInterv.duration;
curT += newInterv.duration;
newInterv.id = "Script:" + sub.replace(/(.+?) ([.0-9]+)us/, "$1");
newInterv.colour = g_used_colours[interval.id];
newInterv.depth = interval.depth+1;
intervals.push(newInterv);
if (newInterv.id in intervals_by_type)
intervals_by_type[newInterv.id].push(intervals.length-1);
else
intervals_by_type[newInterv.id] = [intervals.length-1];
}
}
}
else if (data[i][0] == ITEM_ATTRIBUTE)
{
if (!lastWasEvent && stack.length)
{
if (!stack[stack.length-1].attrs)
stack[stack.length-1].attrs = [];
stack[stack.length-1].attrs.push(data[i][1]);
}
}
}
return { 'frames': frames, 'events': events, 'intervals': intervals, 'intervals_by_type' : intervals_by_type, 'tmin': tmin, 'tmax': tmax };
}
outInterface.data = function() { return g_data; };
outInterface.raw_data = function() { return g_raw_data; };
outInterface.data_by_frame = function() { return g_data_by_frame; };
refresh(callback, tryLive, file);
return outInterface;
};

View File

@ -1,479 +1,479 @@
// Copyright (c) 2016 Wildfire Games
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Handles the drawing of a report
var g_report_draw = (function()
{
var outInterface = {};
var mouse_is_down = null;
function rebuild_canvases(raw_data)
{
g_canvas = {};
g_canvas.canvas_frames = $('<canvas width="1600" height="128"></canvas>').get(0);
g_canvas.threads = {};
for (var thread = 0; thread < raw_data.threads.length; thread++)
g_canvas.threads[thread] = $('<canvas width="1600" height="128"></canvas>').get(0);
g_canvas.canvas_zoom = $('<canvas width="1600" height="192"></canvas>').get(0);
g_canvas.text_output = $('<pre></pre>').get(0);
$('#timelines').empty();
$('#timelines').append("<h3>Main thread frames</h3>");
$('#timelines').append(g_canvas.canvas_frames);
for (var thread = 0; thread < raw_data.threads.length; thread++)
{
$('#timelines').append("<h3>" + raw_data.threads[thread].name + "</h3>");
$('#timelines').append($(g_canvas.threads[thread]));
}
$('#timelines').append("<h3>Zoomed frames</h3>");
$('#timelines').append(g_canvas.canvas_zoom);
$('#timelines').append(g_canvas.text_output);
}
function update_display(report, range)
{
let data = report.data();
let raw_data = report.raw_data();
let main_data = data.threads[g_main_thread];
rebuild_canvases(raw_data);
if (range.seconds)
{
range.tmax = main_data.frames[main_data.frames.length-1].t1;
range.tmin = main_data.frames[main_data.frames.length-1].t1-range.seconds;
}
else if (range.frames)
{
range.tmax = main_data.frames[main_data.frames.length-1].t1;
range.tmin = main_data.frames[main_data.frames.length-1-range.frames].t0;
}
$(g_canvas.text_output).empty();
display_frames(data.threads[g_main_thread], g_canvas.canvas_frames, range);
display_events(data.threads[g_main_thread], g_canvas.canvas_frames, range);
set_frames_zoom_handlers(report, g_canvas.canvas_frames);
set_tooltip_handlers(g_canvas.canvas_frames);
$(g_canvas.canvas_zoom).unbind();
set_zoom_handlers(data.threads[g_main_thread], data.threads[g_main_thread], g_canvas.threads[g_main_thread], g_canvas.canvas_zoom);
set_tooltip_handlers(data.canvas_zoom);
for (var i = 0; i < data.threads.length; i++)
{
$(g_canvas.threads[i]).unbind();
let events = slice_intervals(data.threads[i], range);
display_hierarchy(data.threads[i], events, g_canvas.threads[i], {});
set_zoom_handlers(data.threads[i], events, g_canvas.threads[i], g_canvas.canvas_zoom);
set_tooltip_handlers(g_canvas.threads[i]);
};
}
outInterface.update_display = update_display;
function display_frames(data, canvas, range)
{
canvas._tooltips = [];
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
var xpadding = 8;
var padding_top = 40;
var width = canvas.width - xpadding*2;
var height = canvas.height - padding_top - 4;
var tmin = data.tmin;
var tmax = data.tmax;
var dx = width / (tmax-tmin);
canvas._zoomData = {
'x_to_t': x => tmin + (x - xpadding) / dx,
't_to_x': t => (t - tmin) * dx + xpadding
};
// log 100 scale, skip < 15 ms (60fps)
var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100));
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.fillStyle = 'rgb(255, 255, 255)';
for (var i = 0; i < data.frames.length; ++i)
{
var frame = data.frames[i];
var duration = frame.t1 - frame.t0;
var x0 = xpadding + dx*(frame.t0 - tmin);
var x1 = x0 + dx*duration;
var y1 = canvas.height;
var y0 = y1 * scale(duration*1000);
ctx.beginPath();
ctx.rect(x0, y0, x1-x0, y1-y0);
ctx.stroke();
canvas._tooltips.push({
'x0': x0, 'x1': x1,
'y0': y0, 'y1': y1,
'text': function(frame, duration) { return function() {
var t = '<b>Frame</b><br>';
t += 'Length: ' + time_label(duration) + '<br>';
if (frame.attrs)
{
frame.attrs.forEach(function(attr)
{
t += attr + '<br>';
});
}
return t;
}} (frame, duration)
});
}
[16, 33, 200, 500].forEach(function(t)
{
var y1 = canvas.height;
var y0 = y1 * scale(t);
var y = Math.floor(y0) + 0.5;
ctx.beginPath();
ctx.moveTo(xpadding, y);
ctx.lineTo(canvas.width - xpadding, y);
ctx.strokeStyle = 'rgb(255, 0, 0)';
ctx.stroke();
ctx.fillStyle = 'rgb(255, 0, 0)';
ctx.fillText(t+'ms', 0, y-2);
});
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
ctx.beginPath();
ctx.rect(xpadding + dx*(range.tmin - tmin), 0, dx*(range.tmax - range.tmin), canvas.height);
ctx.fill();
ctx.stroke();
ctx.restore();
}
outInterface.display_frames = display_frames;
function display_events(data, canvas)
{
var ctx = canvas.getContext('2d');
ctx.save();
var x_to_time = canvas._zoomData.x_to_t;
var time_to_x = canvas._zoomData.t_to_x;
for (var i = 0; i < data.events.length; ++i)
{
var event = data.events[i];
if (event.id == '__framestart')
continue;
if (event.id == 'gui event' && event.attrs && event.attrs[0] == 'type: mousemove')
continue;
var x = time_to_x(event.t);
var y = 32;
if (x < 2)
continue;
var x0 = x;
var x1 = x;
var y0 = y-4;
var y1 = y+4;
ctx.strokeStyle = 'rgb(255, 0, 0)';
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
canvas._tooltips.push({
'x0': x0, 'x1': x1,
'y0': y0, 'y1': y1,
'text': function(event) { return function() {
var t = '<b>' + event.id + '</b><br>';
if (event.attrs)
{
event.attrs.forEach(function(attr) {
t += attr + '<br>';
});
}
return t;
}} (event)
});
}
ctx.restore();
}
outInterface.display_events = display_events;
function display_hierarchy(main_data, data, canvas, range, zoom)
{
canvas._tooltips = [];
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.font = '12px sans-serif';
var xpadding = 8;
var padding_top = 40;
var width = canvas.width - xpadding*2;
var height = canvas.height - padding_top - 4;
var tmin, tmax, start, end;
if (range.tmin)
{
tmin = range.tmin;
tmax = range.tmax;
}
else
{
tmin = data.tmin;
tmax = data.tmax;
}
canvas._hierarchyData = { 'range': range, 'tmin': tmin, 'tmax': tmax };
function time_to_x(t)
{
return xpadding + (t - tmin) / (tmax - tmin) * width;
}
function x_to_time(x)
{
return tmin + (x - xpadding) * (tmax - tmin) / width;
}
ctx.save();
ctx.textAlign = 'center';
ctx.strokeStyle = 'rgb(192, 192, 192)';
ctx.beginPath();
var precision = -3;
while ((tmax-tmin)*Math.pow(10, 3+precision) < 25)
++precision;
if (precision > 10)
precision = 10;
if (precision < 0)
precision = 0;
var ticks_per_sec = Math.pow(10, 3+precision);
var major_tick_interval = 5;
for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i)
{
var major = (i % major_tick_interval == 0);
var x = Math.floor(time_to_x(tmin + i/ticks_per_sec));
ctx.moveTo(x-0.5, padding_top - (major ? 4 : 2));
ctx.lineTo(x-0.5, padding_top + height);
if (major)
ctx.fillText((i*1000/ticks_per_sec).toFixed(precision), x, padding_top - 8);
}
ctx.stroke();
ctx.restore();
var BAR_SPACING = 16;
for (var i = 0; i < data.intervals.length; ++i)
{
var interval = data.intervals[i];
if (interval.tmax <= tmin || interval.tmin > tmax)
continue;
var x0 = Math.floor(time_to_x(interval.t0));
var x1 = Math.floor(time_to_x(interval.t1));
if (x1-x0 < 1)
continue;
var y0 = padding_top + interval.depth * BAR_SPACING;
var y1 = y0 + BAR_SPACING;
var label = interval.id;
if (interval.attrs)
{
if (/^\d+$/.exec(interval.attrs[0]))
label += ' ' + interval.attrs[0];
else
label += ' [...]';
}
ctx.fillStyle = interval.colour;
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0);
ctx.fill();
ctx.stroke();
ctx.fillStyle = 'black';
ctx.fillText(label, x0+2, y0+BAR_SPACING-4, Math.max(1, x1-x0-4));
canvas._tooltips.push({
'x0': x0, 'x1': x1,
'y0': y0, 'y1': y1,
'text': function(interval) { return function() {
var t = '<b>' + interval.id + '</b><br>';
t += 'Length: ' + time_label(interval.duration) + '<br>';
if (interval.attrs)
{
interval.attrs.forEach(function(attr) {
t += attr + '<br>';
});
}
return t;
}} (interval)
});
}
for (var i = 0; i < main_data.frames.length; ++i)
{
var frame = main_data.frames[i];
if (frame.t0 < tmin || frame.t0 > tmax)
continue;
var x = Math.floor(time_to_x(frame.t0));
ctx.save();
ctx.lineWidth = 3;
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
ctx.beginPath();
ctx.moveTo(x+0.5, 0);
ctx.lineTo(x+0.5, canvas.height);
ctx.stroke();
ctx.fillText(((frame.t1 - frame.t0) * 1000).toFixed(0)+'ms', x+2, padding_top - 24);
ctx.restore();
}
if (zoom)
{
var x0 = time_to_x(zoom.tmin);
var x1 = time_to_x(zoom.tmax);
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
ctx.beginPath();
ctx.moveTo(x0+0.5, 0.5);
ctx.lineTo(x1+0.5, 0.5);
ctx.lineTo(x1+0.5 + 4, canvas.height-0.5);
ctx.lineTo(x0+0.5 - 4, canvas.height-0.5);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
ctx.restore();
}
outInterface.display_hierarchy = display_hierarchy;
function set_frames_zoom_handlers(report, canvas0)
{
function do_zoom(report, event)
{
var zdata = canvas0._zoomData;
var relativeX = event.pageX - this.offsetLeft;
var relativeY = event.pageY - this.offsetTop;
var width = relativeY / canvas0.height;
width = width*width;
width *= zdata.x_to_t(canvas0.width)/10;
var tavg = zdata.x_to_t(relativeX);
var tmax = tavg + width/2;
var tmin = tavg - width/2;
var range = {'tmin': tmin, 'tmax': tmax};
update_display(report, range);
}
$(canvas0).unbind();
$(canvas0).mousedown(function(event)
{
mouse_is_down = canvas0;
do_zoom.call(this, report, event);
});
$(canvas0).mouseup(function(event)
{
mouse_is_down = null;
});
$(canvas0).mousemove(function(event)
{
if (mouse_is_down)
do_zoom.call(this, report, event);
});
}
function set_zoom_handlers(main_data, data, canvas0, canvas1)
{
function do_zoom(event)
{
var hdata = canvas0._hierarchyData;
function x_to_time(x)
{
return hdata.tmin + x * (hdata.tmax - hdata.tmin) / canvas0.width;
}
var relativeX = event.pageX - this.offsetLeft;
var relativeY = (event.pageY + this.offsetTop) / canvas0.height;
relativeY = relativeY - 0.5;
relativeY *= 5;
relativeY *= relativeY;
var width = relativeY / canvas0.height;
width = width*width;
width = 3 + width * x_to_time(canvas0.width)/10;
var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) };
display_hierarchy(main_data, data, canvas0, hdata.range, zoom);
display_hierarchy(main_data, data, canvas1, zoom, undefined);
set_tooltip_handlers(canvas1);
}
$(canvas0).mousedown(function(event)
{
mouse_is_down = canvas0;
do_zoom.call(this, event);
});
$(canvas0).mouseup(function(event)
{
mouse_is_down = null;
});
$(canvas0).mousemove(function(event)
{
if (mouse_is_down)
do_zoom.call(this, event);
});
}
return outInterface;
// Copyright (c) 2016 Wildfire Games
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Handles the drawing of a report
var g_report_draw = (function()
{
var outInterface = {};
var mouse_is_down = null;
function rebuild_canvases(raw_data)
{
g_canvas = {};
g_canvas.canvas_frames = $('<canvas width="1600" height="128"></canvas>').get(0);
g_canvas.threads = {};
for (var thread = 0; thread < raw_data.threads.length; thread++)
g_canvas.threads[thread] = $('<canvas width="1600" height="128"></canvas>').get(0);
g_canvas.canvas_zoom = $('<canvas width="1600" height="192"></canvas>').get(0);
g_canvas.text_output = $('<pre></pre>').get(0);
$('#timelines').empty();
$('#timelines').append("<h3>Main thread frames</h3>");
$('#timelines').append(g_canvas.canvas_frames);
for (var thread = 0; thread < raw_data.threads.length; thread++)
{
$('#timelines').append("<h3>" + raw_data.threads[thread].name + "</h3>");
$('#timelines').append($(g_canvas.threads[thread]));
}
$('#timelines').append("<h3>Zoomed frames</h3>");
$('#timelines').append(g_canvas.canvas_zoom);
$('#timelines').append(g_canvas.text_output);
}
function update_display(report, range)
{
let data = report.data();
let raw_data = report.raw_data();
let main_data = data.threads[g_main_thread];
rebuild_canvases(raw_data);
if (range.seconds)
{
range.tmax = main_data.frames[main_data.frames.length-1].t1;
range.tmin = main_data.frames[main_data.frames.length-1].t1-range.seconds;
}
else if (range.frames)
{
range.tmax = main_data.frames[main_data.frames.length-1].t1;
range.tmin = main_data.frames[main_data.frames.length-1-range.frames].t0;
}
$(g_canvas.text_output).empty();
display_frames(data.threads[g_main_thread], g_canvas.canvas_frames, range);
display_events(data.threads[g_main_thread], g_canvas.canvas_frames, range);
set_frames_zoom_handlers(report, g_canvas.canvas_frames);
set_tooltip_handlers(g_canvas.canvas_frames);
$(g_canvas.canvas_zoom).unbind();
set_zoom_handlers(data.threads[g_main_thread], data.threads[g_main_thread], g_canvas.threads[g_main_thread], g_canvas.canvas_zoom);
set_tooltip_handlers(data.canvas_zoom);
for (var i = 0; i < data.threads.length; i++)
{
$(g_canvas.threads[i]).unbind();
let events = slice_intervals(data.threads[i], range);
display_hierarchy(data.threads[i], events, g_canvas.threads[i], {});
set_zoom_handlers(data.threads[i], events, g_canvas.threads[i], g_canvas.canvas_zoom);
set_tooltip_handlers(g_canvas.threads[i]);
};
}
outInterface.update_display = update_display;
function display_frames(data, canvas, range)
{
canvas._tooltips = [];
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
var xpadding = 8;
var padding_top = 40;
var width = canvas.width - xpadding*2;
var height = canvas.height - padding_top - 4;
var tmin = data.tmin;
var tmax = data.tmax;
var dx = width / (tmax-tmin);
canvas._zoomData = {
'x_to_t': x => tmin + (x - xpadding) / dx,
't_to_x': t => (t - tmin) * dx + xpadding
};
// log 100 scale, skip < 15 ms (60fps)
var scale = x => 1 - Math.max(0, Math.log(1 + (x-15)/10) / Math.log(100));
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.fillStyle = 'rgb(255, 255, 255)';
for (var i = 0; i < data.frames.length; ++i)
{
var frame = data.frames[i];
var duration = frame.t1 - frame.t0;
var x0 = xpadding + dx*(frame.t0 - tmin);
var x1 = x0 + dx*duration;
var y1 = canvas.height;
var y0 = y1 * scale(duration*1000);
ctx.beginPath();
ctx.rect(x0, y0, x1-x0, y1-y0);
ctx.stroke();
canvas._tooltips.push({
'x0': x0, 'x1': x1,
'y0': y0, 'y1': y1,
'text': function(frame, duration) { return function() {
var t = '<b>Frame</b><br>';
t += 'Length: ' + time_label(duration) + '<br>';
if (frame.attrs)
{
frame.attrs.forEach(function(attr)
{
t += attr + '<br>';
});
}
return t;
}} (frame, duration)
});
}
[16, 33, 200, 500].forEach(function(t)
{
var y1 = canvas.height;
var y0 = y1 * scale(t);
var y = Math.floor(y0) + 0.5;
ctx.beginPath();
ctx.moveTo(xpadding, y);
ctx.lineTo(canvas.width - xpadding, y);
ctx.strokeStyle = 'rgb(255, 0, 0)';
ctx.stroke();
ctx.fillStyle = 'rgb(255, 0, 0)';
ctx.fillText(t+'ms', 0, y-2);
});
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
ctx.beginPath();
ctx.rect(xpadding + dx*(range.tmin - tmin), 0, dx*(range.tmax - range.tmin), canvas.height);
ctx.fill();
ctx.stroke();
ctx.restore();
}
outInterface.display_frames = display_frames;
function display_events(data, canvas)
{
var ctx = canvas.getContext('2d');
ctx.save();
var x_to_time = canvas._zoomData.x_to_t;
var time_to_x = canvas._zoomData.t_to_x;
for (var i = 0; i < data.events.length; ++i)
{
var event = data.events[i];
if (event.id == '__framestart')
continue;
if (event.id == 'gui event' && event.attrs && event.attrs[0] == 'type: mousemove')
continue;
var x = time_to_x(event.t);
var y = 32;
if (x < 2)
continue;
var x0 = x;
var x1 = x;
var y0 = y-4;
var y1 = y+4;
ctx.strokeStyle = 'rgb(255, 0, 0)';
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
canvas._tooltips.push({
'x0': x0, 'x1': x1,
'y0': y0, 'y1': y1,
'text': function(event) { return function() {
var t = '<b>' + event.id + '</b><br>';
if (event.attrs)
{
event.attrs.forEach(function(attr) {
t += attr + '<br>';
});
}
return t;
}} (event)
});
}
ctx.restore();
}
outInterface.display_events = display_events;
function display_hierarchy(main_data, data, canvas, range, zoom)
{
canvas._tooltips = [];
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.font = '12px sans-serif';
var xpadding = 8;
var padding_top = 40;
var width = canvas.width - xpadding*2;
var height = canvas.height - padding_top - 4;
var tmin, tmax, start, end;
if (range.tmin)
{
tmin = range.tmin;
tmax = range.tmax;
}
else
{
tmin = data.tmin;
tmax = data.tmax;
}
canvas._hierarchyData = { 'range': range, 'tmin': tmin, 'tmax': tmax };
function time_to_x(t)
{
return xpadding + (t - tmin) / (tmax - tmin) * width;
}
function x_to_time(x)
{
return tmin + (x - xpadding) * (tmax - tmin) / width;
}
ctx.save();
ctx.textAlign = 'center';
ctx.strokeStyle = 'rgb(192, 192, 192)';
ctx.beginPath();
var precision = -3;
while ((tmax-tmin)*Math.pow(10, 3+precision) < 25)
++precision;
if (precision > 10)
precision = 10;
if (precision < 0)
precision = 0;
var ticks_per_sec = Math.pow(10, 3+precision);
var major_tick_interval = 5;
for (var i = 0; i < (tmax-tmin)*ticks_per_sec; ++i)
{
var major = (i % major_tick_interval == 0);
var x = Math.floor(time_to_x(tmin + i/ticks_per_sec));
ctx.moveTo(x-0.5, padding_top - (major ? 4 : 2));
ctx.lineTo(x-0.5, padding_top + height);
if (major)
ctx.fillText((i*1000/ticks_per_sec).toFixed(precision), x, padding_top - 8);
}
ctx.stroke();
ctx.restore();
var BAR_SPACING = 16;
for (var i = 0; i < data.intervals.length; ++i)
{
var interval = data.intervals[i];
if (interval.tmax <= tmin || interval.tmin > tmax)
continue;
var x0 = Math.floor(time_to_x(interval.t0));
var x1 = Math.floor(time_to_x(interval.t1));
if (x1-x0 < 1)
continue;
var y0 = padding_top + interval.depth * BAR_SPACING;
var y1 = y0 + BAR_SPACING;
var label = interval.id;
if (interval.attrs)
{
if (/^\d+$/.exec(interval.attrs[0]))
label += ' ' + interval.attrs[0];
else
label += ' [...]';
}
ctx.fillStyle = interval.colour;
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.rect(x0-0.5, y0-0.5, x1-x0, y1-y0);
ctx.fill();
ctx.stroke();
ctx.fillStyle = 'black';
ctx.fillText(label, x0+2, y0+BAR_SPACING-4, Math.max(1, x1-x0-4));
canvas._tooltips.push({
'x0': x0, 'x1': x1,
'y0': y0, 'y1': y1,
'text': function(interval) { return function() {
var t = '<b>' + interval.id + '</b><br>';
t += 'Length: ' + time_label(interval.duration) + '<br>';
if (interval.attrs)
{
interval.attrs.forEach(function(attr) {
t += attr + '<br>';
});
}
return t;
}} (interval)
});
}
for (var i = 0; i < main_data.frames.length; ++i)
{
var frame = main_data.frames[i];
if (frame.t0 < tmin || frame.t0 > tmax)
continue;
var x = Math.floor(time_to_x(frame.t0));
ctx.save();
ctx.lineWidth = 3;
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
ctx.beginPath();
ctx.moveTo(x+0.5, 0);
ctx.lineTo(x+0.5, canvas.height);
ctx.stroke();
ctx.fillText(((frame.t1 - frame.t0) * 1000).toFixed(0)+'ms', x+2, padding_top - 24);
ctx.restore();
}
if (zoom)
{
var x0 = time_to_x(zoom.tmin);
var x1 = time_to_x(zoom.tmax);
ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
ctx.fillStyle = 'rgba(128, 128, 255, 0.2)';
ctx.beginPath();
ctx.moveTo(x0+0.5, 0.5);
ctx.lineTo(x1+0.5, 0.5);
ctx.lineTo(x1+0.5 + 4, canvas.height-0.5);
ctx.lineTo(x0+0.5 - 4, canvas.height-0.5);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
ctx.restore();
}
outInterface.display_hierarchy = display_hierarchy;
function set_frames_zoom_handlers(report, canvas0)
{
function do_zoom(report, event)
{
var zdata = canvas0._zoomData;
var relativeX = event.pageX - this.offsetLeft;
var relativeY = event.pageY - this.offsetTop;
var width = relativeY / canvas0.height;
width = width*width;
width *= zdata.x_to_t(canvas0.width)/10;
var tavg = zdata.x_to_t(relativeX);
var tmax = tavg + width/2;
var tmin = tavg - width/2;
var range = {'tmin': tmin, 'tmax': tmax};
update_display(report, range);
}
$(canvas0).unbind();
$(canvas0).mousedown(function(event)
{
mouse_is_down = canvas0;
do_zoom.call(this, report, event);
});
$(canvas0).mouseup(function(event)
{
mouse_is_down = null;
});
$(canvas0).mousemove(function(event)
{
if (mouse_is_down)
do_zoom.call(this, report, event);
});
}
function set_zoom_handlers(main_data, data, canvas0, canvas1)
{
function do_zoom(event)
{
var hdata = canvas0._hierarchyData;
function x_to_time(x)
{
return hdata.tmin + x * (hdata.tmax - hdata.tmin) / canvas0.width;
}
var relativeX = event.pageX - this.offsetLeft;
var relativeY = (event.pageY + this.offsetTop) / canvas0.height;
relativeY = relativeY - 0.5;
relativeY *= 5;
relativeY *= relativeY;
var width = relativeY / canvas0.height;
width = width*width;
width = 3 + width * x_to_time(canvas0.width)/10;
var zoom = { tmin: x_to_time(relativeX-width/2), tmax: x_to_time(relativeX+width/2) };
display_hierarchy(main_data, data, canvas0, hdata.range, zoom);
display_hierarchy(main_data, data, canvas1, zoom, undefined);
set_tooltip_handlers(canvas1);
}
$(canvas0).mousedown(function(event)
{
mouse_is_down = canvas0;
do_zoom.call(this, event);
});
$(canvas0).mouseup(function(event)
{
mouse_is_down = null;
});
$(canvas0).mousemove(function(event)
{
if (mouse_is_down)
do_zoom.call(this, event);
});
}
return outInterface;
})();

View File

@ -1,91 +1,91 @@
<!DOCTYPE html>
<head>
<title>0 A.D. profiler UI</title>
<script src="jquery-1.6.4.js"></script>
<script src="utilities.js"></script>
<script src="ReportDraw.js"></script>
<script src="Profiler2Report.js"></script>
<script src="profiler2.js"></script>
<style>
@keyframes rotate
{
0% {transform:translateY(-50%) rotate(0deg);}
100% {transform:translateY(-50%) rotate(360deg);}
}
html { font-size: 14px; font-family: "Source Code Pro", monospace; padding:10px;}
* { box-sizing:border-box; margin:0; padding: 0; }
canvas { border: 1px #ddd solid; display:block;}
header h1 { font-size:2em; }
header nav { height:50px; margin:5px; border:1px solid gray; }
header nav p { display:inline-block; height:100%; padding: 15px 10px; background:#aaa; cursor:pointer; border-right:1px solid gray; position:relative;}
header nav p.loading { background:#ccf; cursor:progress; padding-right:15px; }
header nav p.loading:after { content:""; position:absolute; right:5px; top:50%; transform:translateY(-50%); height:5px;width:5px;background:#ccc;border:1px solid gray; animation:rotate 2s infinite; }
header nav p.fail { background:#faa;cursor:not-allowed;}
header nav p.active { cursor:pointer; background:#eee; box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.5);font-weight:bold;}
#tooltip { background: #ffd; padding: 4px; font: 12px sans-serif; border: 1px #880 solid; }
#tooltip.long { -moz-column-count: 2; }
#choices { display: flex; flex-wrap:wrap;}
#choices section { position:relative;}
#choices section h2 { height:25px; }
#choices section aside { width:200px; position:absolute; top: 0px; right : 1px; border-left:1px solid gray;border-bottom:1px solid gray; background:rgba(255,255,255,0.5);}
#choices section aside:hover { opacity:0.2; }
#choices section aside p { margin:4px 0px 4px 2px; border-left:15px solid; padding-left: 5px; font-size:0.8rem; }
#choices section input { height:25px; vertical-align:top; }
#choices section label { line-height:25px; padding-left:5px; }
#choices nav { flex-shrink:0; width:300px; height:600px; overflow-y: scroll;}
#choices nav p { margin:2px; cursor:pointer;}
#choices nav p:hover { background:#eee;}
#choices nav p.active { background:#fee;}
#comparison { min-width:400px; }
#comparison table { border-collapse:collapse; margin: 20px 10px;}
#comparison td,#comparison th { min-width:80px;height:25px;text-align: center;}
#comparison th { border: 1px solid #888; background:#eee; }
#comparison td { border: 1px solid #bbb; }
</style>
</head>
<body>
<button onclick="save_as_file()">Save Live Report to file</button>
<header>
<h1>Open reports</h1>
<p>Use the input field below to load a new report (from JSON)</p>
<input type="file" id="report_load_input" name="files[]" />
<nav></nav>
</header>
<p>Click on the following timelines to zoom.</p>
</a><div id="timelines"></div>
<div id="choices">
<div style="width:100%">
<h1>Analysis</h1>
<p>Click on any of the event names in "choices" to see more details about them. Load more reports to compare.</p>
</div>
<section id="frequency_graph">
<h2>Frequency Graph</h2>
<input type="checkbox" id="fulln" name="fulln" value="fulln" onchange="update_analysis()"><label for="fulln">Show for all frames</label>
<div style="position:relative">
<aside></aside>
<canvas id="canvas_frequency" width="600" height="600"></canvas
html5 > </div>
</section>
<section id="history_graph">
<h2>Frame-by-Frame Graph</h2>
<input type="range" id="smooth" name="smooth" onchange="update_analysis()" min="0" max="10" step="1" value="3"/><label for="smooth">Degree of smoothing</label>
<div style="position:relative">
<aside></aside>
<div style="width:602px;overflow:auto;"><canvas id="canvas_history" width="600" height="600"></canvas></div>
</div>
</section>
<nav>
<h3>Choices</h3>
</nav>
<div id="comparison">
<h3>Report Comparison</h3>
</div>
</div>
<div id="tooltip" style="position: absolute; visibility: hidden"></div>
<pre id="debug"></pre>
</body>
<!DOCTYPE html>
<head>
<title>0 A.D. profiler UI</title>
<script src="jquery-1.6.4.js"></script>
<script src="utilities.js"></script>
<script src="ReportDraw.js"></script>
<script src="Profiler2Report.js"></script>
<script src="profiler2.js"></script>
<style>
@keyframes rotate
{
0% {transform:translateY(-50%) rotate(0deg);}
100% {transform:translateY(-50%) rotate(360deg);}
}
html { font-size: 14px; font-family: "Source Code Pro", monospace; padding:10px;}
* { box-sizing:border-box; margin:0; padding: 0; }
canvas { border: 1px #ddd solid; display:block;}
header h1 { font-size:2em; }
header nav { height:50px; margin:5px; border:1px solid gray; }
header nav p { display:inline-block; height:100%; padding: 15px 10px; background:#aaa; cursor:pointer; border-right:1px solid gray; position:relative;}
header nav p.loading { background:#ccf; cursor:progress; padding-right:15px; }
header nav p.loading:after { content:""; position:absolute; right:5px; top:50%; transform:translateY(-50%); height:5px;width:5px;background:#ccc;border:1px solid gray; animation:rotate 2s infinite; }
header nav p.fail { background:#faa;cursor:not-allowed;}
header nav p.active { cursor:pointer; background:#eee; box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.5);font-weight:bold;}
#tooltip { background: #ffd; padding: 4px; font: 12px sans-serif; border: 1px #880 solid; }
#tooltip.long { -moz-column-count: 2; }
#choices { display: flex; flex-wrap:wrap;}
#choices section { position:relative;}
#choices section h2 { height:25px; }
#choices section aside { width:200px; position:absolute; top: 0px; right : 1px; border-left:1px solid gray;border-bottom:1px solid gray; background:rgba(255,255,255,0.5);}
#choices section aside:hover { opacity:0.2; }
#choices section aside p { margin:4px 0px 4px 2px; border-left:15px solid; padding-left: 5px; font-size:0.8rem; }
#choices section input { height:25px; vertical-align:top; }
#choices section label { line-height:25px; padding-left:5px; }
#choices nav { flex-shrink:0; width:300px; height:600px; overflow-y: scroll;}
#choices nav p { margin:2px; cursor:pointer;}
#choices nav p:hover { background:#eee;}
#choices nav p.active { background:#fee;}
#comparison { min-width:400px; }
#comparison table { border-collapse:collapse; margin: 20px 10px;}
#comparison td,#comparison th { min-width:80px;height:25px;text-align: center;}
#comparison th { border: 1px solid #888; background:#eee; }
#comparison td { border: 1px solid #bbb; }
</style>
</head>
<body>
<button onclick="save_as_file()">Save Live Report to file</button>
<header>
<h1>Open reports</h1>
<p>Use the input field below to load a new report (from JSON)</p>
<input type="file" id="report_load_input" name="files[]" />
<nav></nav>
</header>
<p>Click on the following timelines to zoom.</p>
</a><div id="timelines"></div>
<div id="choices">
<div style="width:100%">
<h1>Analysis</h1>
<p>Click on any of the event names in "choices" to see more details about them. Load more reports to compare.</p>
</div>
<section id="frequency_graph">
<h2>Frequency Graph</h2>
<input type="checkbox" id="fulln" name="fulln" value="fulln" onchange="update_analysis()"><label for="fulln">Show for all frames</label>
<div style="position:relative">
<aside></aside>
<canvas id="canvas_frequency" width="600" height="600"></canvas
html5 > </div>
</section>
<section id="history_graph">
<h2>Frame-by-Frame Graph</h2>
<input type="range" id="smooth" name="smooth" onchange="update_analysis()" min="0" max="10" step="1" value="3"/><label for="smooth">Degree of smoothing</label>
<div style="position:relative">
<aside></aside>
<div style="width:602px;overflow:auto;"><canvas id="canvas_history" width="600" height="600"></canvas></div>
</div>
</section>
<nav>
<h3>Choices</h3>
</nav>
<div id="comparison">
<h3>Report Comparison</h3>
</div>
</div>
<div id="tooltip" style="position: absolute; visibility: hidden"></div>
<pre id="debug"></pre>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,192 +1,192 @@
// Copyright (c) 2016 Wildfire Games
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Various functions used by several of the tiles.
function hslToRgb(h, s, l, a)
{
var r, g, b;
if (s == 0)
{
r = g = b = l;
}
else
{
function hue2rgb(p, q, t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return 'rgba(' + Math.floor(r * 255) + ',' + Math.floor(g * 255) + ',' + Math.floor(b * 255) + ',' + a + ')';
}
function new_colour(id)
{
var hs = [0, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5];
var ss = [1, 0.5];
var ls = [0.8, 0.6, 0.9, 0.7];
return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1);
}
function graph_colour(id)
{
var hs = [0, 1/3, 2/3, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5];
return hslToRgb(hs[id % hs.length], 0.7, 0.5, 1);
}
function concat_events(data)
{
var events = [];
data.events.forEach(function(ev) {
ev.pop(); // remove the dummy null markers
Array.prototype.push.apply(events, ev);
});
return events;
}
function time_label(t, precision = 2)
{
if (t < 0)
return "-" + time_label(-t, precision);
if (t > 1e-3)
return (t * 1e3).toFixed(precision) + 'ms';
else
return (t * 1e6).toFixed(precision) + 'us';
}
function slice_intervals(data, range)
{
if (!data.intervals.length)
return {"tmin":0,"tmax":0,"intervals":[]};
var tmin = 0;
var tmax = 0;
if (range.seconds && data.frames.length)
{
tmax = data.frames[data.frames.length-1].t1;
tmin = data.frames[data.frames.length-1].t1-range.seconds;
}
else if (range.frames && data.frames.length)
{
tmax = data.frames[data.frames.length-1].t1;
tmin = data.frames[data.frames.length-1-range.frames].t0;
}
else
{
tmax = range.tmax;
tmin = range.tmin;
}
var events = { "tmin" : tmin, "tmax" : tmax, "intervals" : [] };
for (let itv in data.intervals)
{
let interval = data.intervals[itv];
if (interval.t1 > tmin && interval.t0 < tmax)
events.intervals.push(interval);
}
return events;
}
function smooth_1D(array, i, distance)
{
let value = 0;
let total = 0;
for (let j = i - distance; j <= i + distance; j++)
{
value += array[j]*(1+distance*distance - (j-i)*(j-i) );
total += (1+distance*distance - (j-i)*(j-i) );
}
return value/total;
}
function smooth_1D_array(array, distance)
{
let copied = array.slice(0);
for (let i =0; i < array.length; ++i)
{
let value = 0;
let total = 0;
for (let j = i - distance; j <= i + distance; j++)
{
value += array[j]*(1+distance*distance - (j-i)*(j-i) );
total += (1+distance*distance - (j-i)*(j-i) );
}
copied[i] = value/total;
}
return copied;
}
function set_tooltip_handlers(canvas)
{
function do_tooltip(event)
{
var tooltips = canvas._tooltips;
if (!tooltips)
return;
var relativeX = event.pageX - this.getBoundingClientRect().left - window.scrollX;
var relativeY = event.pageY - this.getBoundingClientRect().top - window.scrollY;
var text = undefined;
for (var i = 0; i < tooltips.length; ++i)
{
var t = tooltips[i];
if (t.x0-1 <= relativeX && relativeX <= t.x1+1 && t.y0 <= relativeY && relativeY <= t.y1)
{
text = t.text();
break;
}
}
if (text)
{
if (text.length > 512)
$('#tooltip').addClass('long');
else
$('#tooltip').removeClass('long');
$('#tooltip').css('left', (event.pageX+16)+'px');
$('#tooltip').css('top', (event.pageY+8)+'px');
$('#tooltip').html(text);
$('#tooltip').css('visibility', 'visible');
}
else
{
$('#tooltip').css('visibility', 'hidden');
}
}
$(canvas).mousemove(function(event) {
do_tooltip.call(this, event);
});
$(canvas).mouseleave(function(event) {
$('#tooltip').css('visibility', 'hidden');
});
// Copyright (c) 2016 Wildfire Games
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// Various functions used by several of the tiles.
function hslToRgb(h, s, l, a)
{
var r, g, b;
if (s == 0)
{
r = g = b = l;
}
else
{
function hue2rgb(p, q, t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return 'rgba(' + Math.floor(r * 255) + ',' + Math.floor(g * 255) + ',' + Math.floor(b * 255) + ',' + a + ')';
}
function new_colour(id)
{
var hs = [0, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5];
var ss = [1, 0.5];
var ls = [0.8, 0.6, 0.9, 0.7];
return hslToRgb(hs[id % hs.length], ss[Math.floor(id / hs.length) % ss.length], ls[Math.floor(id / (hs.length*ss.length)) % ls.length], 1);
}
function graph_colour(id)
{
var hs = [0, 1/3, 2/3, 2/4, 3/4, 1/5, 3/5, 2/5, 4/5];
return hslToRgb(hs[id % hs.length], 0.7, 0.5, 1);
}
function concat_events(data)
{
var events = [];
data.events.forEach(function(ev) {
ev.pop(); // remove the dummy null markers
Array.prototype.push.apply(events, ev);
});
return events;
}
function time_label(t, precision = 2)
{
if (t < 0)
return "-" + time_label(-t, precision);
if (t > 1e-3)
return (t * 1e3).toFixed(precision) + 'ms';
else
return (t * 1e6).toFixed(precision) + 'us';
}
function slice_intervals(data, range)
{
if (!data.intervals.length)
return {"tmin":0,"tmax":0,"intervals":[]};
var tmin = 0;
var tmax = 0;
if (range.seconds && data.frames.length)
{
tmax = data.frames[data.frames.length-1].t1;
tmin = data.frames[data.frames.length-1].t1-range.seconds;
}
else if (range.frames && data.frames.length)
{
tmax = data.frames[data.frames.length-1].t1;
tmin = data.frames[data.frames.length-1-range.frames].t0;
}
else
{
tmax = range.tmax;
tmin = range.tmin;
}
var events = { "tmin" : tmin, "tmax" : tmax, "intervals" : [] };
for (let itv in data.intervals)
{
let interval = data.intervals[itv];
if (interval.t1 > tmin && interval.t0 < tmax)
events.intervals.push(interval);
}
return events;
}
function smooth_1D(array, i, distance)
{
let value = 0;
let total = 0;
for (let j = i - distance; j <= i + distance; j++)
{
value += array[j]*(1+distance*distance - (j-i)*(j-i) );
total += (1+distance*distance - (j-i)*(j-i) );
}
return value/total;
}
function smooth_1D_array(array, distance)
{
let copied = array.slice(0);
for (let i =0; i < array.length; ++i)
{
let value = 0;
let total = 0;
for (let j = i - distance; j <= i + distance; j++)
{
value += array[j]*(1+distance*distance - (j-i)*(j-i) );
total += (1+distance*distance - (j-i)*(j-i) );
}
copied[i] = value/total;
}
return copied;
}
function set_tooltip_handlers(canvas)
{
function do_tooltip(event)
{
var tooltips = canvas._tooltips;
if (!tooltips)
return;
var relativeX = event.pageX - this.getBoundingClientRect().left - window.scrollX;
var relativeY = event.pageY - this.getBoundingClientRect().top - window.scrollY;
var text = undefined;
for (var i = 0; i < tooltips.length; ++i)
{
var t = tooltips[i];
if (t.x0-1 <= relativeX && relativeX <= t.x1+1 && t.y0 <= relativeY && relativeY <= t.y1)
{
text = t.text();
break;
}
}
if (text)
{
if (text.length > 512)
$('#tooltip').addClass('long');
else
$('#tooltip').removeClass('long');
$('#tooltip').css('left', (event.pageX+16)+'px');
$('#tooltip').css('top', (event.pageY+8)+'px');
$('#tooltip').html(text);
$('#tooltip').css('visibility', 'visible');
}
else
{
$('#tooltip').css('visibility', 'hidden');
}
}
$(canvas).mousemove(function(event) {
do_tooltip.call(this, event);
});
$(canvas).mouseleave(function(event) {
$('#tooltip').css('visibility', 'hidden');
});
}

View File

@ -1,99 +1,99 @@
# http://code.djangoproject.com/attachment/ticket/5908/cycle.py
from django.utils.translation import ungettext, ugettext as _
from django.utils.encoding import force_unicode
from django import template
from django.template import defaultfilters
from django.template import Node, Variable
from django.conf import settings
from itertools import cycle as itertools_cycle
register = template.Library()
class SafeCycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars
self.cycle_iter = itertools_cycle(cyclevars)
self.variable_name = variable_name
def render(self, context):
if context.has_key('forloop'):
if not context.get(self):
context[self] = True
self.cycle_iter = itertools_cycle(self.cyclevars)
value = self.cycle_iter.next()
value = Variable(value).resolve(context)
if self.variable_name:
context[self.variable_name] = value
return value
#@register.tag
def safe_cycle(parser, token):
"""
Cycles among the given strings each time this tag is encountered.
Within a loop, cycles among the given strings each time through
the loop::
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
Outside of a loop, give the values a unique name the first time you call
it, then use that name each sucessive time through::
<tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings.
"""
# Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: this stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using
# a global variable, which would make cycle names have to be unique across
# *all* templates.
args = token.split_contents()
if len(args) < 2:
raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
if ',' in args[1]:
# Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
# case.
args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
if len(args) == 2:
# {% cycle foo %} case.
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template."
" '%s' is not defined" % name)
if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
if len(args) > 4 and args[-2] == 'as':
name = args[-1]
node = SafeCycleNode(args[1:-2], name)
if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {}
parser._namedCycleNodes[name] = node
else:
node = SafeCycleNode(args[1:])
return node
safe_cycle = register.tag(safe_cycle)
# http://code.djangoproject.com/attachment/ticket/5908/cycle.py
from django.utils.translation import ungettext, ugettext as _
from django.utils.encoding import force_unicode
from django import template
from django.template import defaultfilters
from django.template import Node, Variable
from django.conf import settings
from itertools import cycle as itertools_cycle
register = template.Library()
class SafeCycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
self.cyclevars = cyclevars
self.cycle_iter = itertools_cycle(cyclevars)
self.variable_name = variable_name
def render(self, context):
if context.has_key('forloop'):
if not context.get(self):
context[self] = True
self.cycle_iter = itertools_cycle(self.cyclevars)
value = self.cycle_iter.next()
value = Variable(value).resolve(context)
if self.variable_name:
context[self.variable_name] = value
return value
#@register.tag
def safe_cycle(parser, token):
"""
Cycles among the given strings each time this tag is encountered.
Within a loop, cycles among the given strings each time through
the loop::
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
Outside of a loop, give the values a unique name the first time you call
it, then use that name each sucessive time through::
<tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings.
"""
# Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know.
#
# Ugly hack warning: this stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using
# a global variable, which would make cycle names have to be unique across
# *all* templates.
args = token.split_contents()
if len(args) < 2:
raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
if ',' in args[1]:
# Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
# case.
args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
if len(args) == 2:
# {% cycle foo %} case.
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template."
" '%s' is not defined" % name)
if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
if len(args) > 4 and args[-2] == 'as':
name = args[-1]
node = SafeCycleNode(args[1:-2], name)
if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {}
parser._namedCycleNodes[name] = node
else:
node = SafeCycleNode(args[1:])
return node
safe_cycle = register.tag(safe_cycle)